From 8a2d00f330f9150d0d08cacb6382406eebef692a Mon Sep 17 00:00:00 2001 From: Javid Khan Date: Thu, 2 Jul 2026 21:49:32 +0530 Subject: bound cdc-data endpoints against descriptor length in acm_open --- src/class/cdc/cdc_host.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/class/cdc/cdc_host.c b/src/class/cdc/cdc_host.c index 30afc2f5c..902316029 100644 --- a/src/class/cdc/cdc_host.c +++ b/src/class/cdc/cdc_host.c @@ -1031,6 +1031,7 @@ static uint16_t acm_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, u // Open notification endpoint of control interface if any if (itf_desc->bNumEndpoints == 1) { + TU_ASSERT(tu_desc_in_bounds(p_desc, desc_end), 0); TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(p_desc), 0); const tusb_desc_endpoint_t *desc_ep = (const tusb_desc_endpoint_t *)p_desc; TU_ASSERT(tuh_edpt_open(daddr, desc_ep), 0); @@ -1040,12 +1041,13 @@ static uint16_t acm_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, u } //------------- Data Interface (if any) -------------// - if (TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) { + if (tu_desc_in_bounds(p_desc, desc_end) && TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) { const tusb_desc_interface_t *data_itf = (const tusb_desc_interface_t *)p_desc; if (data_itf->bInterfaceClass == TUSB_CLASS_CDC_DATA) { p_desc = tu_desc_next(p_desc); // next to endpoint descriptor - // data endpoints expected to be in pairs + // data endpoints expected to be in pairs, make sure both fit before reading them + TU_ASSERT((uint16_t)(desc_end - p_desc) >= 2 * sizeof(tusb_desc_endpoint_t), 0); TU_ASSERT(open_ep_stream_pair(p_cdc, (const tusb_desc_endpoint_t *)p_desc), 0); p_desc += data_itf->bNumEndpoints * sizeof(tusb_desc_endpoint_t); } -- cgit v1.3.1 From 28daf9eb87fe4aaa6d2208d565b4bf53ace25730 Mon Sep 17 00:00:00 2001 From: Javid Khan Date: Fri, 3 Jul 2026 22:53:06 +0530 Subject: guard acm_open endpoint reads against short bLength descriptors --- src/class/cdc/cdc_host.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/class/cdc/cdc_host.c b/src/class/cdc/cdc_host.c index 902316029..4f02face0 100644 --- a/src/class/cdc/cdc_host.c +++ b/src/class/cdc/cdc_host.c @@ -716,7 +716,9 @@ bool cdch_xfer_cb(uint8_t daddr, uint8_t ep_addr, xfer_result_t event, uint32_t //--------------------------------------------------------------------+ static bool open_ep_stream_pair(cdch_interface_t *p_cdc, tusb_desc_endpoint_t const *desc_ep) { for (size_t i = 0; i < 2; i++) { - TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && TUSB_XFER_BULK == desc_ep->bmAttributes.xfer, 0); + // pin bLength so tu_desc_next() below cannot walk the second endpoint past a caller-checked bound + TU_ASSERT(sizeof(tusb_desc_endpoint_t) == desc_ep->bLength && + TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && TUSB_XFER_BULK == desc_ep->bmAttributes.xfer, 0); TU_ASSERT(tuh_edpt_open(p_cdc->daddr, desc_ep)); const uint8_t ep_dir = tu_edpt_dir(desc_ep->bEndpointAddress); tu_edpt_stream_t *stream = (ep_dir == TUSB_DIR_IN) ? &p_cdc->stream.rx : &p_cdc->stream.tx; @@ -1031,7 +1033,8 @@ static uint16_t acm_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, u // Open notification endpoint of control interface if any if (itf_desc->bNumEndpoints == 1) { - TU_ASSERT(tu_desc_in_bounds(p_desc, desc_end), 0); + // whole endpoint descriptor must fit: tuh_edpt_open reads the full struct regardless of bLength + TU_ASSERT(p_desc + sizeof(tusb_desc_endpoint_t) <= desc_end, 0); TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(p_desc), 0); const tusb_desc_endpoint_t *desc_ep = (const tusb_desc_endpoint_t *)p_desc; TU_ASSERT(tuh_edpt_open(daddr, desc_ep), 0); @@ -1041,13 +1044,13 @@ static uint16_t acm_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, u } //------------- Data Interface (if any) -------------// - if (tu_desc_in_bounds(p_desc, desc_end) && TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) { + if (p_desc + sizeof(tusb_desc_interface_t) <= desc_end && TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) { const tusb_desc_interface_t *data_itf = (const tusb_desc_interface_t *)p_desc; if (data_itf->bInterfaceClass == TUSB_CLASS_CDC_DATA) { p_desc = tu_desc_next(p_desc); // next to endpoint descriptor // data endpoints expected to be in pairs, make sure both fit before reading them - TU_ASSERT((uint16_t)(desc_end - p_desc) >= 2 * sizeof(tusb_desc_endpoint_t), 0); + TU_ASSERT(p_desc + 2 * sizeof(tusb_desc_endpoint_t) <= desc_end, 0); TU_ASSERT(open_ep_stream_pair(p_cdc, (const tusb_desc_endpoint_t *)p_desc), 0); p_desc += data_itf->bNumEndpoints * sizeof(tusb_desc_endpoint_t); } -- cgit v1.3.1 From ea39a8f4b12e2fc572931d7da4f3025589c536e7 Mon Sep 17 00:00:00 2001 From: Javid Khan Date: Sat, 4 Jul 2026 10:41:47 +0530 Subject: advance acm_open descriptor walk by fixed struct sizes --- src/class/cdc/cdc_host.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/class/cdc/cdc_host.c b/src/class/cdc/cdc_host.c index 4f02face0..aeaab1c21 100644 --- a/src/class/cdc/cdc_host.c +++ b/src/class/cdc/cdc_host.c @@ -1040,19 +1040,21 @@ static uint16_t acm_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, u TU_ASSERT(tuh_edpt_open(daddr, desc_ep), 0); p_cdc->ep_notif = desc_ep->bEndpointAddress; - p_desc = tu_desc_next(p_desc); + // advance by the fixed struct size, not device-supplied bLength, so p_desc stays inside the checked window + p_desc += sizeof(tusb_desc_endpoint_t); } //------------- Data Interface (if any) -------------// if (p_desc + sizeof(tusb_desc_interface_t) <= desc_end && TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) { const tusb_desc_interface_t *data_itf = (const tusb_desc_interface_t *)p_desc; if (data_itf->bInterfaceClass == TUSB_CLASS_CDC_DATA) { - p_desc = tu_desc_next(p_desc); // next to endpoint descriptor + p_desc += sizeof(tusb_desc_interface_t); // fixed struct size to endpoint descriptor, not device bLength - // data endpoints expected to be in pairs, make sure both fit before reading them + // open_ep_stream_pair consumes exactly two endpoints; require that count and that both fit before reading them + TU_ASSERT(data_itf->bNumEndpoints == 2, 0); TU_ASSERT(p_desc + 2 * sizeof(tusb_desc_endpoint_t) <= desc_end, 0); TU_ASSERT(open_ep_stream_pair(p_cdc, (const tusb_desc_endpoint_t *)p_desc), 0); - p_desc += data_itf->bNumEndpoints * sizeof(tusb_desc_endpoint_t); + p_desc += 2 * sizeof(tusb_desc_endpoint_t); } } -- cgit v1.3.1 From eebd0c1c1c04d68a56eac09f56ed16e0ee570a0d Mon Sep 17 00:00:00 2001 From: Javid Khan Date: Sun, 5 Jul 2026 16:26:13 +0530 Subject: bound acm_open functional descriptor walk against desc_end --- src/class/cdc/cdc_host.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/class/cdc/cdc_host.c b/src/class/cdc/cdc_host.c index aeaab1c21..6045350a8 100644 --- a/src/class/cdc/cdc_host.c +++ b/src/class/cdc/cdc_host.c @@ -1022,8 +1022,11 @@ static uint16_t acm_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, u p_desc = tu_desc_next(p_desc); // Communication Functional Descriptors - while ((p_desc < desc_end) && (TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc))) { - if (CDC_FUNC_DESC_ABSTRACT_CONTROL_MANAGEMENT == cdc_functional_desc_typeof(p_desc)) { + // need the 3-byte header (bLength/bDescriptorType/bDescriptorSubType) in bounds before reading it, and a + // bLength >= 3 both keeps those reads valid and stops a zero-length descriptor from spinning the walk + while (p_desc + 3 <= desc_end && TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && tu_desc_len(p_desc) >= 3) { + if (CDC_FUNC_DESC_ABSTRACT_CONTROL_MANAGEMENT == cdc_functional_desc_typeof(p_desc) && + p_desc + sizeof(cdc_desc_func_acm_t) <= desc_end) { // save ACM bmCapabilities p_cdc->acm.capability = ((cdc_desc_func_acm_t const *) p_desc)->bmCapabilities; } -- cgit v1.3.1 From 3beaa799a92005bf1982d9c8bf65a368f5b1946f Mon Sep 17 00:00:00 2001 From: Jie Feng Date: Mon, 6 Jul 2026 00:15:22 +0800 Subject: Set larger AT32 FSDEV PMA area --- README.rst | 2 +- src/common/tusb_mcu.h | 4 +++- src/portable/st/stm32_fsdev/fsdev_at32.h | 2 +- src/portable/st/stm32_fsdev/fsdev_common.c | 5 +++++ 4 files changed, 10 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/README.rst b/README.rst index e5806bafc..a3a3147c4 100644 --- a/README.rst +++ b/README.rst @@ -184,7 +184,7 @@ Supported CPUs | | MAX32 650, 666, 690, | ✅ | | ✅ | musb | 1-dir ep | | | MAX78002 | | | | | | +--------------+-----------------------------+--------+------+-----------+------------------------+--------------------+ -| Artery AT32 | F403a_407, F413 | ✅ | | | stm32_fsdev | 512 USB RAM | +| Artery AT32 | F403a_407, F413 | ✅ | | | stm32_fsdev | 768 USB RAM | | +-----------------------------+--------+------+-----------+------------------------+--------------------+ | | F415, F435_437, F423, | ✅ | ✅ | | dwc2 | | | | F425, F45x | | | | | | diff --git a/src/common/tusb_mcu.h b/src/common/tusb_mcu.h index 1f2afb03a..4e61041ba 100644 --- a/src/common/tusb_mcu.h +++ b/src/common/tusb_mcu.h @@ -673,7 +673,9 @@ #elif TU_CHECK_MCU(OPT_MCU_AT32F403A_407, OPT_MCU_AT32F413) #define TUP_USBIP_FSDEV #define TUP_USBIP_FSDEV_AT32 - #define CFG_TUSB_FSDEV_PMA_SIZE 512u + #define CFG_TUSB_FSDEV_PMA_SIZE 768u + #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE 2 + #define CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE 4 #elif TU_CHECK_MCU(OPT_MCU_AT32F415) #define TUP_USBIP_DWC2 diff --git a/src/portable/st/stm32_fsdev/fsdev_at32.h b/src/portable/st/stm32_fsdev/fsdev_at32.h index 212c3b86d..9138dc101 100644 --- a/src/portable/st/stm32_fsdev/fsdev_at32.h +++ b/src/portable/st/stm32_fsdev/fsdev_at32.h @@ -17,7 +17,7 @@ #define FSDEV_USE_SBUF_ISO 0 #define FSDEV_REG_BASE (APB1PERIPH_BASE + 0x00005C00UL) -#define FSDEV_PMA_BASE (APB1PERIPH_BASE + 0x00006000UL) +#define FSDEV_PMA_BASE (APB1PERIPH_BASE + 0x00007800UL) #ifndef CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP #define CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP 0 diff --git a/src/portable/st/stm32_fsdev/fsdev_common.c b/src/portable/st/stm32_fsdev/fsdev_common.c index 2b573899a..def3b2c2e 100644 --- a/src/portable/st/stm32_fsdev/fsdev_common.c +++ b/src/portable/st/stm32_fsdev/fsdev_common.c @@ -33,6 +33,11 @@ void fsdev_core_reset(void) { // Clear pending interrupts FSDEV_REG->ISTR = 0; + + #if (CFG_TUSB_MCU == OPT_MCU_AT32F403A_407) || (CFG_TUSB_MCU == OPT_MCU_AT32F413) + // Enable larger PMA area + CRM->misc1_bit.usbbufs = TRUE; + #endif } // De-initialize the USB Core -- cgit v1.3.1 From be8e6990cee5f7c2d5f09fc8494a1159c5c7c5a7 Mon Sep 17 00:00:00 2001 From: Javid Khan Date: Tue, 7 Jul 2026 05:26:03 +0530 Subject: gate acm_open descriptor bounds behind CFG_TUH_VALIDATION_LEVEL add a three-level TUSB_VALIDATION_NONE/BASIC/STRICT knob and default CFG_TUH_VALIDATION_LEVEL to BASIC, then make acm_open the first user so the enumeration bounds checks compile out at NONE for trusted-device setups and stay on by default. --- src/class/cdc/cdc_host.c | 28 ++++++++++++++++++++-------- src/tusb_option.h | 13 +++++++++++++ 2 files changed, 33 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/class/cdc/cdc_host.c b/src/class/cdc/cdc_host.c index 6045350a8..d1cbc2924 100644 --- a/src/class/cdc/cdc_host.c +++ b/src/class/cdc/cdc_host.c @@ -714,11 +714,21 @@ bool cdch_xfer_cb(uint8_t daddr, uint8_t ep_addr, xfer_result_t event, uint32_t //--------------------------------------------------------------------+ // Enumeration //--------------------------------------------------------------------+ + + // Descriptor-walk hardening gated by CFG_TUH_VALIDATION_LEVEL: at NONE the guards collapse to a pass so + // trusted-device setups pay no code size; at BASIC (default) the walk stays inside the enumeration buffer. + #if CFG_TUH_VALIDATION_LEVEL >= TUSB_VALIDATION_BASIC + #define TU_DESC_VALIDATE(_cond) (_cond) + #else + #define TU_DESC_VALIDATE(_cond) (true) + #endif + static bool open_ep_stream_pair(cdch_interface_t *p_cdc, tusb_desc_endpoint_t const *desc_ep) { for (size_t i = 0; i < 2; i++) { // pin bLength so tu_desc_next() below cannot walk the second endpoint past a caller-checked bound - TU_ASSERT(sizeof(tusb_desc_endpoint_t) == desc_ep->bLength && - TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && TUSB_XFER_BULK == desc_ep->bmAttributes.xfer, 0); + TU_ASSERT(TU_DESC_VALIDATE(sizeof(tusb_desc_endpoint_t) == desc_ep->bLength) && + TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && TUSB_XFER_BULK == desc_ep->bmAttributes.xfer, + 0); TU_ASSERT(tuh_edpt_open(p_cdc->daddr, desc_ep)); const uint8_t ep_dir = tu_edpt_dir(desc_ep->bEndpointAddress); tu_edpt_stream_t *stream = (ep_dir == TUSB_DIR_IN) ? &p_cdc->stream.rx : &p_cdc->stream.tx; @@ -1024,9 +1034,10 @@ static uint16_t acm_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, u // Communication Functional Descriptors // need the 3-byte header (bLength/bDescriptorType/bDescriptorSubType) in bounds before reading it, and a // bLength >= 3 both keeps those reads valid and stops a zero-length descriptor from spinning the walk - while (p_desc + 3 <= desc_end && TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && tu_desc_len(p_desc) >= 3) { + while ((p_desc < desc_end) && TU_DESC_VALIDATE(p_desc + 3 <= desc_end) && + TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && TU_DESC_VALIDATE(tu_desc_len(p_desc) >= 3)) { if (CDC_FUNC_DESC_ABSTRACT_CONTROL_MANAGEMENT == cdc_functional_desc_typeof(p_desc) && - p_desc + sizeof(cdc_desc_func_acm_t) <= desc_end) { + TU_DESC_VALIDATE(p_desc + sizeof(cdc_desc_func_acm_t) <= desc_end)) { // save ACM bmCapabilities p_cdc->acm.capability = ((cdc_desc_func_acm_t const *) p_desc)->bmCapabilities; } @@ -1037,7 +1048,7 @@ static uint16_t acm_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, u // Open notification endpoint of control interface if any if (itf_desc->bNumEndpoints == 1) { // whole endpoint descriptor must fit: tuh_edpt_open reads the full struct regardless of bLength - TU_ASSERT(p_desc + sizeof(tusb_desc_endpoint_t) <= desc_end, 0); + TU_ASSERT(TU_DESC_VALIDATE(p_desc + sizeof(tusb_desc_endpoint_t) <= desc_end), 0); TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(p_desc), 0); const tusb_desc_endpoint_t *desc_ep = (const tusb_desc_endpoint_t *)p_desc; TU_ASSERT(tuh_edpt_open(daddr, desc_ep), 0); @@ -1048,14 +1059,15 @@ static uint16_t acm_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, u } //------------- Data Interface (if any) -------------// - if (p_desc + sizeof(tusb_desc_interface_t) <= desc_end && TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) { + if (TU_DESC_VALIDATE(p_desc + sizeof(tusb_desc_interface_t) <= desc_end) && + TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) { const tusb_desc_interface_t *data_itf = (const tusb_desc_interface_t *)p_desc; if (data_itf->bInterfaceClass == TUSB_CLASS_CDC_DATA) { p_desc += sizeof(tusb_desc_interface_t); // fixed struct size to endpoint descriptor, not device bLength // open_ep_stream_pair consumes exactly two endpoints; require that count and that both fit before reading them - TU_ASSERT(data_itf->bNumEndpoints == 2, 0); - TU_ASSERT(p_desc + 2 * sizeof(tusb_desc_endpoint_t) <= desc_end, 0); + TU_ASSERT(TU_DESC_VALIDATE(data_itf->bNumEndpoints == 2), 0); + TU_ASSERT(TU_DESC_VALIDATE(p_desc + 2 * sizeof(tusb_desc_endpoint_t) <= desc_end), 0); TU_ASSERT(open_ep_stream_pair(p_cdc, (const tusb_desc_endpoint_t *)p_desc), 0); p_desc += 2 * sizeof(tusb_desc_endpoint_t); } diff --git a/src/tusb_option.h b/src/tusb_option.h index e19ee1629..de8439cfb 100644 --- a/src/tusb_option.h +++ b/src/tusb_option.h @@ -239,6 +239,15 @@ #define OPT_MODE_HIGH_SPEED 0x0400u ///< High Speed #define OPT_MODE_SPEED_MASK 0xff00u +//--------------------------------------------------------------------+ +// Descriptor Validation Level +// How much the stack hardens itself against mal-configured or hostile devices, traded against code size. +// Higher levels add more checks; set CFG_TUD_VALIDATION_LEVEL / CFG_TUH_VALIDATION_LEVEL to pick one. +//--------------------------------------------------------------------+ +#define TUSB_VALIDATION_NONE 0 ///< trusted devices only, minimal code size +#define TUSB_VALIDATION_BASIC 1 ///< default: mal-configured devices, no OOB reads / zero-length loops +#define TUSB_VALIDATION_STRICT 2 ///< reject malformed/hostile descriptors, stricter class validation + //--------------------------------------------------------------------+ // Include tusb_config.h //--------------------------------------------------------------------+ @@ -694,6 +703,10 @@ #ifndef CFG_TUH_ENUMERATION_BUFSIZE #define CFG_TUH_ENUMERATION_BUFSIZE 256 #endif + + #ifndef CFG_TUH_VALIDATION_LEVEL + #define CFG_TUH_VALIDATION_LEVEL TUSB_VALIDATION_BASIC + #endif #endif // CFG_TUH_ENABLED // Attribute to place data in accessible RAM for host controller (default: CFG_TUSB_MEM_SECTION) -- cgit v1.3.1 From 33cabfe3f045a89ffdcfadce6ed5331c8b7bc870 Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 9 Jul 2026 23:38:08 +0700 Subject: 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 Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg --- src/class/vendor/vendor_device.c | 597 +++++++++++++++++++++++++++++++++++++-- src/class/vendor/vendor_device.h | 161 +++++++++++ 2 files changed, 739 insertions(+), 19 deletions(-) (limited to 'src') 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 } @@ -173,6 +285,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 //--------------------------------------------------------------------+ @@ -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 -- cgit v1.3.1 From fd63ad6c2b102d3ea981047e75b43c8db723534e Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 9 Jul 2026 23:38:11 +0700 Subject: usbd: forward vendor EP0 requests; clear ep state on iso activate Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg --- src/device/usbd.c | 84 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 55 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/device/usbd.c b/src/device/usbd.c index 7a6e13f8d..5471e132d 100644 --- a/src/device/usbd.c +++ b/src/device/usbd.c @@ -256,7 +256,7 @@ static const usbd_class_driver_t _usbd_driver[] = { .deinit = vendord_deinit, .reset = vendord_reset, .open = vendord_open, - .control_xfer_cb = tud_vendor_control_xfer_cb, + .control_xfer_cb = vendord_control_xfer_cb, .xfer_cb = vendord_xfer_cb, .xfer_isr = NULL, .sof = NULL @@ -418,6 +418,7 @@ TU_ATTR_ALWAYS_INLINE static inline bool queue_event(dcd_event_t const * event, //--------------------------------------------------------------------+ static bool usbd_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); static bool process_setup_received(uint8_t rhport, tusb_control_request_t const * p_request); +static bool process_get_status(uint8_t rhport, tusb_control_request_t const * request, uint16_t status); static bool process_set_config(uint8_t rhport, uint8_t cfg_num); static bool process_get_descriptor(uint8_t rhport, tusb_control_request_t const * p_request); @@ -1041,9 +1042,7 @@ static bool process_std_device_request(uint8_t rhport, tusb_control_request_t co // Device status bit mask // - Bit 0: Self Powered TODO must invoke callback to get actual status // - Bit 1: Remote Wakeup enabled - uint16_t status = (uint16_t) _usbd_dev.dev_state_bm; - tud_control_xfer(rhport, p_request, &status, 2); - return true; + return process_get_status(rhport, p_request, (uint16_t) _usbd_dev.dev_state_bm); } default: @@ -1053,6 +1052,14 @@ static bool process_std_device_request(uint8_t rhport, tusb_control_request_t co } +// Reply to a standard GET_STATUS (device/interface/endpoint) with its 2-byte status word. +// GET_STATUS is Device-to-host only; reject a mis-directed (OUT) request rather than handing +// usbd the address of a stack local to write host data into after this frame has returned. +static bool process_get_status(uint8_t rhport, tusb_control_request_t const * request, uint16_t status) { + TU_VERIFY(request->bmRequestType_bit.direction == TUSB_DIR_IN); + return tud_control_xfer(rhport, request, &status, 2); +} + // This handles the actual request and its response. // Returns false if unable to complete the request, causing caller to stall control endpoints. static bool process_setup_received(uint8_t rhport, tusb_control_request_t const * p_request) { @@ -1150,9 +1157,18 @@ static bool process_setup_received(uint8_t rhport, tusb_control_request_t const } case TUSB_REQ_SET_INTERFACE: + // A class that implements altsettings handles SET_INTERFACE itself and returns true, + // so reaching here means the class does not — where only alt 0 is valid. Any non-zero + // alt (unimplemented, or rejected as invalid by the class) is a Request Error (stall). + TU_VERIFY(tu_u16_low(p_request->wValue) == 0); tud_control_status(rhport, p_request); break; + case TUSB_REQ_GET_STATUS: + // USB 2.0 9.4.5: interface GET_STATUS returns 2 reserved (zero) bytes + TU_VERIFY(process_get_status(rhport, p_request, 0x0000)); + break; + default: return false; } } @@ -1175,35 +1191,34 @@ static bool process_setup_received(uint8_t rhport, tusb_control_request_t const } else { // Handle STD request to endpoint switch (p_request->bRequest) { //-V2520 - case TUSB_REQ_GET_STATUS: { - uint16_t status = usbd_edpt_stalled(rhport, ep_addr) ? 0x0001u : 0x0000u; - tud_control_xfer(rhport, p_request, &status, 2); - } - break; + case TUSB_REQ_GET_STATUS: + // USB 2.0 9.4.5: endpoint GET_STATUS bit 0 = Halt + TU_VERIFY(process_get_status(rhport, p_request, usbd_edpt_stalled(rhport, ep_addr) ? 0x0001u : 0x0000u)); + break; case TUSB_REQ_CLEAR_FEATURE: case TUSB_REQ_SET_FEATURE: { - if ( TUSB_REQ_FEATURE_EDPT_HALT == p_request->wValue ) { - if ( TUSB_REQ_CLEAR_FEATURE == p_request->bRequest ) { - usbd_edpt_clear_stall(rhport, ep_addr); - }else { - usbd_edpt_stall(rhport, ep_addr); - } + // ENDPOINT_HALT is the only endpoint feature; it exists only on a non-control endpoint + // that an interface actually owns. Any other selector, the control endpoint (EP0 has no + // Halt feature, USB 2.0 9.4.9), or an endpoint no driver owns is a Request Error (stall). + TU_VERIFY(TUSB_REQ_FEATURE_EDPT_HALT == p_request->wValue); + TU_VERIFY(ep_num != 0); + TU_VERIFY(driver != NULL); + + if ( TUSB_REQ_CLEAR_FEATURE == p_request->bRequest ) { + usbd_edpt_clear_stall(rhport, ep_addr); + } else { + usbd_edpt_stall(rhport, ep_addr); } - if (driver != NULL) { - // Some classes such as USBTMC needs to clear/re-init its buffer when receiving CLEAR_FEATURE request - // We will also forward std request targeted endpoint to class drivers as well + // Some classes such as USBTMC need to clear/re-init their buffer on CLEAR_FEATURE. + // Clear complete callback if driver set since it can also stall the request. + (void) invoke_class_control(rhport, driver, p_request); + ctrl_xfer->complete_cb = NULL; - // STD request must always be ACKed regardless of driver returned value - // Also clear complete callback if driver set since it can also stall the request. - (void) invoke_class_control(rhport, driver, p_request); - ctrl_xfer->complete_cb = NULL; - - // skip ZLP status if driver already did that - if (!(_usbd_dev.ep_status[0][TUSB_DIR_IN] & TU_EDPT_STATE_BUSY)) { - tud_control_status(rhport, p_request); - } + // STD request must always be ACKed; skip ZLP status if driver already did that. + if (!(_usbd_dev.ep_status[0][TUSB_DIR_IN] & TU_EDPT_STATE_BUSY)) { + tud_control_status(rhport, p_request); } } break; @@ -1648,10 +1663,21 @@ void usbd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { uint8_t const epnum = tu_edpt_number(ep_addr); uint8_t const dir = tu_edpt_dir(ep_addr); - // only clear if currently stalled TU_LOG_USBD(" Clear Stall EP %02X\r\n", ep_addr); + const bool was_stalled = (_usbd_dev.ep_status[epnum][dir] & TU_EDPT_STATE_STALLED) != 0; dcd_edpt_clear_stall(rhport, ep_addr); - _usbd_dev.ep_status[epnum][dir] &= (uint8_t) ~(TU_EDPT_STATE_STALLED | TU_EDPT_STATE_BUSY); + // Clear STALLED|BUSY unconditionally (long-standing behavior; some classes, e.g. audio's + // set-interface, call this on a non-stalled endpoint solely to drop a leftover BUSY bit). + // Only release the CLAIMED ownership bit when the endpoint was actually stalled: the stall + // aborts the in-flight transfer in the dcd with no completion event to release the claim, so + // clearing it here prevents starvation. On a non-stalled clear (e.g. a data-toggle reset) a + // transfer may still be legitimately claimed by another task, so keep CLAIMED to preserve the + // claim->xfer mutual exclusion. + uint8_t clear_mask = TU_EDPT_STATE_STALLED | TU_EDPT_STATE_BUSY; + if (was_stalled) { + clear_mask |= TU_EDPT_STATE_CLAIMED; + } + _usbd_dev.ep_status[epnum][dir] &= (uint8_t) ~clear_mask; } bool usbd_edpt_stalled(uint8_t rhport, uint8_t ep_addr) { -- cgit v1.3.1 From dfb53cb30c62015fb642d7b841725293d8b950a5 Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 9 Jul 2026 23:38:15 +0700 Subject: dcd: route samd/rusb2/ip3511/nrf5x through iso alloc/activate Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg --- src/common/tusb_mcu.h | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/common/tusb_mcu.h b/src/common/tusb_mcu.h index 1f2afb03a..0959ac3a9 100644 --- a/src/common/tusb_mcu.h +++ b/src/common/tusb_mcu.h @@ -141,7 +141,6 @@ #elif TU_CHECK_MCU(OPT_MCU_NRF5X) // 8 CBI + 1 ISO #define TUP_DCD_ENDPOINT_MAX 9 - #define TUP_DCD_EDPT_CLOSE_API #elif TU_CHECK_MCU(OPT_MCU_NRF54) #define TUP_USBIP_DWC2 @@ -741,11 +740,9 @@ #define TU_ATTR_FAST_FUNC #endif -#if defined(TUP_USBIP_IP3511) || defined(TUP_USBIP_RUSB2) - #define TUP_DCD_EDPT_CLOSE_API -#endif - -// USBIP implement dcd_edpt_close() and does not support ISO alloc & activate API +// TUP_DCD_EDPT_CLOSE_API is deprecated: these USBIPs implement dcd_edpt_close() and lack the +// ISO alloc & activate API. IP3511, RUSB2 and NRF5X have been migrated to ISO_ALLOC; the remaining +// CLOSE_API MCUs (mm32, pic, da1469x, f1c100s, ch32-usbhs) are pending per-board verification. #ifndef TUP_DCD_EDPT_CLOSE_API #define TUP_DCD_EDPT_ISO_ALLOC #endif -- cgit v1.3.1 From 8ec71dca0d81c646bb0cee895f9e7ce91c780bd3 Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 9 Jul 2026 23:38:18 +0700 Subject: dcd(samd): implement iso alloc/activate Reserve the bank SIZE bucket once, re-enable per altsetting, and scrub the bank-ready state so a stale armed bank cannot send before the class re-arms. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg --- src/portable/microchip/samd/dcd_samd.c | 44 ++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/portable/microchip/samd/dcd_samd.c b/src/portable/microchip/samd/dcd_samd.c index 32ddd3422..54ef34c8e 100644 --- a/src/portable/microchip/samd/dcd_samd.c +++ b/src/portable/microchip/samd/dcd_samd.c @@ -229,15 +229,49 @@ bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * desc_edpt) bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { (void) rhport; - (void) ep_addr; - (void)largest_packet_size; - return false; + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + // Reserve the endpoint bank with the largest packet size (persists across altsettings). The + // buffer address/count are filled per-transfer in dcd_edpt_xfer; only the SIZE bucket is fixed. + UsbDeviceDescBank* bank = &sram_registers[epnum][dir]; + uint32_t size_value = 0; + while (size_value < 7) { + if (1 << (size_value + 3) >= largest_packet_size) { + break; + } + size_value++; + } + if ( size_value == 7 && largest_packet_size > 1023 ) return false; + + bank->PCKSIZE.bit.SIZE = size_value; + return true; } bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) { (void)rhport; - (void)desc_ep; - return false; + uint8_t const epnum = tu_edpt_number(desc_ep->bEndpointAddress); + uint8_t const dir = tu_edpt_dir(desc_ep->bEndpointAddress); + + // Configure and enable the ISO endpoint on altsetting selection (bank SIZE already reserved by + // dcd_edpt_iso_alloc). Mirrors the per-direction setup in dcd_edpt_open(), plus a bank scrub: + // under ISO_ALLOC the EP is never disabled on alt0 (usbd_edpt_close is a no-op), so the bank-ready + // state from the previous streaming session survives into re-activation. Leave the EP un-armed so + // a stale bank can't move a packet before dcd_edpt_xfer re-arms it (a leftover BK1RDY with a stale + // BYTE_COUNT would otherwise babble on the first IN token after re-selecting alt1). + UsbDeviceEndpoint* ep = &USB->DEVICE.DeviceEndpoint[epnum]; + if ( dir == TUSB_DIR_OUT ) { + ep->EPCFG.bit.EPTYPE0 = desc_ep->bmAttributes.xfer + 1; + ep->EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_STALLRQ0 | USB_DEVICE_EPSTATUSCLR_DTGLOUT; + ep->EPSTATUSSET.reg = USB_DEVICE_EPSTATUSSET_BK0RDY; // OUT: not ready to receive until armed + ep->EPINTENSET.bit.TRCPT0 = true; + } else { + ep->EPCFG.bit.EPTYPE1 = desc_ep->bmAttributes.xfer + 1; + ep->EPSTATUSCLR.reg = USB_DEVICE_EPSTATUSCLR_STALLRQ1 | USB_DEVICE_EPSTATUSCLR_DTGLIN | + USB_DEVICE_EPSTATUSCLR_BK1RDY; // IN: clear stale "loaded" bank + ep->EPINTENSET.bit.TRCPT1 = true; + } + return true; } void dcd_edpt_close_all (uint8_t rhport) -- cgit v1.3.1 From 7b1eb4f862a40c7abab4891abf1b5968693752b5 Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 9 Jul 2026 23:38:21 +0700 Subject: dcd(rusb2): iso alloc/activate; bound the FIFO-ready wait An unpolled full iso-IN pipe keeps FRDY low forever and froze the stack with IRQs masked; bound the spin and abort the FIFO access. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg --- src/portable/renesas/rusb2/dcd_rusb2.c | 175 +++++++++++++++++++++++++++------ 1 file changed, 143 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/portable/renesas/rusb2/dcd_rusb2.c b/src/portable/renesas/rusb2/dcd_rusb2.c index f0ef9738b..5e42f63f5 100644 --- a/src/portable/renesas/rusb2/dcd_rusb2.c +++ b/src/portable/renesas/rusb2/dcd_rusb2.c @@ -28,6 +28,9 @@ typedef struct { uint8_t ep; /* an assigned endpoint address */ uint8_t ff; /* `buf` is TU_FUFO or POD */ + bool queued; /* a transfer is submitted and not yet completed (independent of `buf`, which is + NULL for a zero-length read) -- used to decide clear-stall re-arm */ + bool zlp_pending; /* a zero-length IN packet couldn't be queued at submit (FIFO full); retry on BRDY */ } pipe_state_t; typedef struct @@ -121,9 +124,19 @@ static uint16_t edpt_max_packet_size(rusb2_reg_t *rusb, unsigned num) { return rusb->PIPEMAXP; } -static inline void pipe_wait_for_ready(rusb2_reg_t * rusb, unsigned num) { - while ( rusb->D0FIFOSEL_b.CURPIPE != num ) {} - while ( !rusb->D0FIFOCTR_b.FRDY ) {} +// Select the D0FIFO for `num` and wait until its buffer is ready for CPU access. Both flags +// normally settle within a few cycles (the pipe was just armed, or a BRDY freed a plane). But an +// IN pipe whose double buffer is already full stalls FRDY until the host drains it, and a +// no-handshake iso IN endpoint the host has stopped polling never drains at all — so FRDY would +// hang forever. This runs with the USB IRQ masked, so a naked spin freezes the whole stack; bound +// it and let the caller abort the FIFO access. Returns false on timeout. +#define RUSB2_FIFO_READY_SPIN 100000u +static inline bool pipe_wait_for_ready(rusb2_reg_t *rusb, unsigned num) { + uint32_t spin = RUSB2_FIFO_READY_SPIN; + while ( rusb->D0FIFOSEL_b.CURPIPE != num ) { if (!spin--) return false; } + spin = RUSB2_FIFO_READY_SPIN; + while ( !rusb->D0FIFOCTR_b.FRDY ) { if (!spin--) return false; } + return true; } //--------------------------------------------------------------------+ @@ -201,6 +214,12 @@ static bool pipe0_xfer_out(rusb2_reg_t *rusb) { pipe->remaining = rem - len; if ((len < mps) || (rem == len)) { pipe->buf = NULL; + // Flow-control the single-buffer control pipe: NAK further OUT until usbd arms the next + // data-stage chunk. usbd receives a multi-packet control-OUT one CFG_TUD_ENDPOINT0_SIZE + // packet per submit; without this the DCP auto-accepts the next back-to-back packet into the + // just-emptied buffer and the following BRDY (remaining==0) BCLR-discards it, dropping 64 + // bytes mid-transfer (e.g. usbtest ctrl_out 512B). RA4M1 UM R01UH0887 DCPCTR.PID. + rusb->DCPCTR = RUSB2_PIPE_CTR_PID_NAK; return true; } @@ -226,7 +245,12 @@ static bool pipe_xfer_in(rusb2_reg_t* rusb, unsigned num) } const uint16_t mps = edpt_max_packet_size(rusb, num); - pipe_wait_for_ready(rusb, num); + if (!pipe_wait_for_ready(rusb, num)) { + // Buffer never came ready (double-buffered IN pipe full, host not draining). Drop this load; + // the transfer stays pending and is retried when a BRDY frees a plane or the pipe is re-armed. + rusb->D0FIFOSEL = 0; + return false; + } uint16_t len = tu_min16(rem, mps); void *buf = pipe->buf; @@ -267,7 +291,10 @@ static bool pipe_xfer_out(rusb2_reg_t* rusb, unsigned num) rusb->D0FIFOSEL = fifo_sel; const uint16_t mps = edpt_max_packet_size(rusb, num); - pipe_wait_for_ready(rusb, num); + if (!pipe_wait_for_ready(rusb, num)) { + rusb->D0FIFOSEL = 0; + return false; // FIFO not ready; leave the receive pending (BRDY re-enters when data arrives) + } const uint16_t vld = (uint16_t)rusb->D0FIFOCTR_b.DTLN; const uint16_t len = tu_min16(tu_min16(rem, mps), vld); @@ -370,6 +397,24 @@ static bool process_pipe0_xfer(rusb2_reg_t *rusb, int buffer_type, uint8_t ep_ad return true; } +// Queue a zero-length IN packet. Returns false if the FIFO buffer wasn't free (double-buffered pipe +// full, host not draining) so BVAL couldn't be written -- the caller retries on the next BRDY. +static bool pipe_zlp_in(rusb2_reg_t *rusb, unsigned num) { + rusb->D0FIFOSEL = (uint16_t) num; + const bool ready = pipe_wait_for_ready(rusb, num); + if (ready) { + rusb->D0FIFOCTR = RUSB2_CFIFOCTR_BVAL_Msk; + } + rusb->D0FIFOSEL = 0; + // deselect completes within a few bus cycles (not host-dependent), but bound it anyway: this + // runs with the USB IRQ masked, where any stuck spin freezes the whole stack + uint32_t spin = RUSB2_FIFO_READY_SPIN; + while (rusb->D0FIFOSEL_b.CURPIPE) { + if (!spin--) { break; } + } + return ready; +} + static bool process_pipe_xfer(rusb2_reg_t* rusb, int buffer_type, uint8_t ep_addr, void* buffer, uint16_t total_bytes) { const unsigned epn = tu_edpt_number(ep_addr); @@ -379,23 +424,20 @@ static bool process_pipe_xfer(rusb2_reg_t* rusb, int buffer_type, uint8_t ep_add TU_ASSERT(num); pipe_state_t *pipe = &_dcd.pipe[num]; - pipe->ff = buffer_type; - pipe->buf = buffer; - pipe->length = total_bytes; - pipe->remaining = total_bytes; + pipe->ff = buffer_type; + pipe->buf = buffer; + pipe->length = total_bytes; + pipe->remaining = total_bytes; + pipe->queued = true; + pipe->zlp_pending = false; if (dir) { /* IN */ if (total_bytes) { pipe_xfer_in(rusb, num); } else { - /* ZLP */ - rusb->D0FIFOSEL = num; - pipe_wait_for_ready(rusb, num); - rusb->D0FIFOCTR = RUSB2_CFIFOCTR_BVAL_Msk; - rusb->D0FIFOSEL = 0; - /* if CURPIPE bits changes, check written value */ - while (rusb->D0FIFOSEL_b.CURPIPE) {} + /* ZLP: if the FIFO buffer isn't free yet, defer the queue to the next BRDY (see process_pipe_brdy) */ + pipe->zlp_pending = !pipe_zlp_in(rusb, num); } } else { // OUT @@ -448,7 +490,15 @@ static void process_pipe_brdy(uint8_t rhport, unsigned num) if (dir) { /* IN */ - completed = pipe_xfer_in(rusb, num); + if (pipe->zlp_pending) { + // The submit-time ZLP couldn't be queued (FIFO full); a freed buffer plane lets us queue it + // now. Don't report completion until the ZLP is actually queued (and then sent, next BRDY), + // otherwise a spurious BRDY would complete a zero-length IN the host never received. + pipe->zlp_pending = !pipe_zlp_in(rusb, num); + completed = false; + } else { + completed = pipe_xfer_in(rusb, num); + } } else { // OUT if (num) { @@ -458,6 +508,7 @@ static void process_pipe_brdy(uint8_t rhport, unsigned num) } } if (completed) { + pipe->queued = false; dcd_event_xfer_complete(rhport, pipe->ep, pipe->length - pipe->remaining, XFER_RESULT_SUCCESS, true); @@ -704,8 +755,14 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * ep_desc) } } - const unsigned num = find_pipe(xfer); - TU_ASSERT(num); + // Re-opening an endpoint must reuse its pipe: usbd_edpt_close() is a no-op on ISO_ALLOC ports, + // so a class's close/open across SET_INTERFACE (e.g. video's notification endpoint) would + // otherwise allocate a second pipe with the same EPNUM and leak pipes until exhaustion. + unsigned num = _dcd.ep[dir][epn]; + if (num == 0) { + num = find_pipe(xfer); + TU_ASSERT(num); + } _dcd.pipe[num].ep = ep_addr; _dcd.ep[dir][epn] = num; @@ -748,6 +805,8 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * ep_desc) return true; } +static void edpt_close(uint8_t rhport, uint8_t ep_addr); + void dcd_edpt_close_all(uint8_t rhport) { unsigned i = TU_ARRAY_SIZE(_dcd.pipe); @@ -757,12 +816,14 @@ void dcd_edpt_close_all(uint8_t rhport) if (!ep_addr) { continue; } - dcd_edpt_close(rhport, (uint8_t)ep_addr); + edpt_close(rhport, (uint8_t)ep_addr); } dcd_int_enable(rhport); } -void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr) +// Internal helper: on this (ISO_ALLOC) IP the stack no longer calls dcd_edpt_close(); only +// dcd_edpt_close_all() uses it to tear down each pipe. +static void edpt_close(uint8_t rhport, uint8_t ep_addr) { rusb2_reg_t * rusb = RUSB2_REG(rhport); const unsigned epn = tu_edpt_number(ep_addr); @@ -774,24 +835,68 @@ void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr) *ctr = 0; rusb->PIPESEL = (uint16_t)num; rusb->PIPECFG = 0; - _dcd.pipe[num].ep = 0; + _dcd.pipe[num].ep = 0; + _dcd.pipe[num].queued = false; + _dcd.pipe[num].zlp_pending = false; _dcd.ep[dir][epn] = 0; } -#if 0 bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { - (void)rhport; - (void)ep_addr; - (void)largest_packet_size; - return false; + rusb2_reg_t * rusb = RUSB2_REG(rhport); + const unsigned epn = tu_edpt_number(ep_addr); + const unsigned dir = tu_edpt_dir(ep_addr); + + // Fullspeed ISO is limited to 256 bytes + if (!rusb2_is_highspeed_rhport(rhport) && largest_packet_size > 256) { + return false; + } + + // Reserve an ISO-capable pipe (1 or 2) once; it persists across altsetting changes so + // dcd_edpt_iso_activate() only has to re-arm it in place (no pipe free/realloc, which on this + // shared-register IP would churn PIPESEL/PIPECFG and disturb the other pipes). + const unsigned num = find_pipe(TUSB_XFER_ISOCHRONOUS); + TU_ASSERT(num); + _dcd.pipe[num].ep = ep_addr; + _dcd.ep[dir][epn] = num; + + dcd_int_disable(rhport); + if (rusb2_is_highspeed_rhport(rhport)) { + // FIXME (as in dcd_edpt_open): PIPEBUF is a PIPESEL-windowed register (RA6M5 UM §29.2.35) so it + // must be written AFTER PIPESEL selects this pipe, and the fixed BUFNMB=0x08 overlaps every + // HS pipe — a real per-pipe buffer allocator is needed. Left as-is: no RA6M5 HS board on + // the HIL rig to validate a change, and the current mis-ordered write is inert on FS/RA4M1. + rusb->PIPEBUF = 0x7C08; + } + rusb->PIPESEL = (uint16_t) num; + rusb->PIPEMAXP = largest_packet_size; + volatile uint16_t *ctr = get_pipectr(rusb, num); + *ctr = RUSB2_PIPE_CTR_ACLRM_Msk | RUSB2_PIPE_CTR_SQCLR_Msk; + *ctr = 0; // leave the pipe NAKing until activated + rusb->PIPECFG = (uint16_t) ((dir << 4) | epn | RUSB2_PIPECFG_TYPE_ISO | RUSB2_PIPECFG_DBLB_Msk); + rusb->BRDYSTS = (uint16_t) (0x3FFu ^ TU_BIT(num)); + rusb->BRDYENB |= TU_BIT(num); + dcd_int_enable(rhport); + return true; } bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) { - (void)rhport; - (void)desc_ep; - return false; + rusb2_reg_t * rusb = RUSB2_REG(rhport); + const uint8_t ep_addr = desc_ep->bEndpointAddress; + const unsigned epn = tu_edpt_number(ep_addr); + const unsigned dir = tu_edpt_dir(ep_addr); + const unsigned num = _dcd.ep[dir][epn]; + TU_ASSERT(num); // must have been iso-alloc'd + + dcd_int_disable(rhport); + rusb->PIPESEL = (uint16_t) num; + rusb->PIPEMAXP = tu_edpt_packet_size(desc_ep); + volatile uint16_t *ctr = get_pipectr(rusb, num); + *ctr = RUSB2_PIPE_CTR_ACLRM_Msk | RUSB2_PIPE_CTR_SQCLR_Msk; // abort in-flight + reset data toggle + *ctr = 0; + *ctr = RUSB2_PIPE_CTR_PID_BUF; // enable + dcd_int_enable(rhport); + return true; } -#endif bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr) { @@ -847,7 +952,13 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) } else { const unsigned num = _dcd.ep[0][tu_edpt_number(ep_addr)]; rusb->PIPESEL = (uint16_t)num; - if (rusb->PIPECFG_b.TYPE != 1) { + // Non-bulk OUT re-enables straight away. Bulk OUT is normally armed together with its transaction + // counter (TRE) by process_pipe_xfer(), so we don't blindly re-enable it here — but if a receive + // was already armed (still queued), SQCLR above just left it NAKing. Re-assert BUF so it keeps + // receiving; the class driver still considers that read submitted and never re-arms it, so + // otherwise the endpoint NAKs forever (usbtest toggle test 29 clears the halt on an armed pipe). + // `queued` (not `buf`) is the armed test: a zero-length OUT read has buf==NULL yet is armed. + if (rusb->PIPECFG_b.TYPE != 1 || _dcd.pipe[num].queued) { *ctr = RUSB2_PIPE_CTR_PID_BUF; } } -- cgit v1.3.1 From 93b57197f9080f756e1f986235151e3eadcb7ea3 Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 9 Jul 2026 23:38:24 +0700 Subject: dcd(ip3511): iso alloc/activate; retire armed buffers via EPSKIP Clear Active before Stall so a queued endpoint actually halts (UM11126 41.8.1); use the sanctioned EPSKIP+wait sequence for stall/reopen/activate. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg --- src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c | 76 ++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c b/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c index aa0307d25..3e9091589 100644 --- a/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c +++ b/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c @@ -158,6 +158,10 @@ typedef struct // - 55 usb0 (FS) has 5x2 endpoints, usb1 (HS) has 6x2 endpoints #define MAX_EP_PAIRS 6 +// Bounded spin waiting for hardware to clear an EPSKIP bit when retiring a still-armed endpoint on +// reopen (dcd_edpt_open). Hardware clears it within a (micro)frame; the guard only avoids a hang. +#define IP3511_EPSKIP_SPIN 100000u + // NOTE data will be transferred as soon as dcd get request by dcd_pipe(_queue)_xfer using double buffering. // current_td is used to keep track of number of remaining & xferred bytes of the current request. typedef struct @@ -337,12 +341,29 @@ void dcd_sof_enable(uint8_t rhport, bool en) //--------------------------------------------------------------------+ // DCD Endpoint Port //--------------------------------------------------------------------+ +// Retire a still-armed (Active) endpoint the sanctioned way before its command/status entry is +// rewritten (halt, reopen, altsetting switch). UM11126 §41.7.6/§41.8.3: write EPSKIP and wait for +// hardware to clear the bit, then Active is safe to clear — a bare Active=0 can race a mid-packet +// buffer. Bounded: hardware clears EPSKIP within a (micro)frame; the guard only prevents a hang. +static void edpt_skip_active(uint8_t rhport, uint8_t ep_id) { + ep_cmd_sts_t* ep_cs = get_ep_cs(ep_id); + if ( ep_cs[0].cmd_sts.active || ep_cs[1].cmd_sts.active ) { + dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs; + dcd_reg->EPSKIP |= TU_BIT(ep_id); + uint32_t guard = IP3511_EPSKIP_SPIN; + while ( (dcd_reg->EPSKIP & TU_BIT(ep_id)) && guard-- ) {} + } + ep_cs[0].cmd_sts.active = ep_cs[1].cmd_sts.active = 0; +} + void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { - (void) rhport; - // TODO cannot able to STALL Control OUT endpoint !!!!! FIXME try some walk-around uint8_t const ep_id = ep_addr2id(ep_addr); + // Retire any armed buffer before setting Stall: the hardware services an armed (Active) buffer + // instead of returning STALL, so a halt requested while a transfer is queued would not actually + // stall the endpoint (usbtest case 13), and Active+Stall must not both be set. + edpt_skip_active(rhport, ep_id); _dcd.ep[ep_id][0].cmd_sts.stall = 1; } @@ -362,9 +383,15 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) //------------- Prepare Queue Head -------------// uint8_t ep_id = ep_addr2id(p_endpoint_desc->bEndpointAddress); ep_cmd_sts_t* ep_cs = get_ep_cs(ep_id); + dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs; - // Check if endpoint is available - TU_ASSERT( ep_cs[0].cmd_sts.disable && ep_cs[1].cmd_sts.disable ); + // usbd_edpt_close() is a no-op on ISO_ALLOC ports, so an endpoint a class closed then reopened + // across SET_INTERFACE (e.g. the video notification or audio streaming endpoint) is still armed + // here rather than disabled. Retire it (edpt_skip_active) before reconfiguring. + if ( !(ep_cs[0].cmd_sts.disable && ep_cs[1].cmd_sts.disable) ) { + edpt_skip_active(rhport, ep_id); + ep_cs[0].cmd_sts.disable = ep_cs[1].cmd_sts.disable = 1; + } edpt_reset(rhport, ep_id); @@ -389,7 +416,6 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) } // Enable EP interrupt - dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs; dcd_reg->INTEN |= TU_BIT(ep_id); return true; @@ -404,29 +430,35 @@ void dcd_edpt_close_all (uint8_t rhport) } } -void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - +bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { + (void) largest_packet_size; + // Reserve the endpoint command/status entry once (persists across altsetting changes); the + // buffer pointer is filled per-transfer, so nothing to pre-allocate. Mirrors the ISO branch of + // dcd_edpt_open(). uint8_t ep_id = ep_addr2id(ep_addr); - _dcd.ep[ep_id][0].cmd_sts.active = _dcd.ep[ep_id][0].cmd_sts.active = 0; // TODO proper way is to EPSKIP then wait ep[][].active then write ep[][].disable (see table 778 in LPC55S69 Use Manual) - _dcd.ep[ep_id][0].cmd_sts.disable = _dcd.ep[ep_id][1].cmd_sts.disable = 1; -} + ep_cmd_sts_t* ep_cs = get_ep_cs(ep_id); + TU_ASSERT( ep_cs[0].cmd_sts.disable && ep_cs[1].cmd_sts.disable ); -#if 0 -bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { - (void)rhport; - (void)ep_addr; - (void)largest_packet_size; - return false; + edpt_reset(rhport, ep_id); + ep_cs[0].cmd_sts.type = 1; // ISO + + dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs; + dcd_reg->INTEN |= TU_BIT(ep_id); + return true; } bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) { - (void)rhport; - (void)desc_ep; - return false; + // (Re)activate on altsetting selection: retire a buffer still armed from the previous altsetting + // (the hardware keeps servicing an Active buffer across SET_INTERFACE, fighting the class's fresh + // transfer), clear stall and reset the data toggle. The class re-arms via dcd_edpt_xfer(). + uint8_t ep_id = ep_addr2id(desc_ep->bEndpointAddress); + ep_cmd_sts_t* ep_cs = get_ep_cs(ep_id); + edpt_skip_active(rhport, ep_id); + ep_cs[0].cmd_sts.stall = 0; + ep_cs[0].cmd_sts.toggle_reset = 1; + ep_cs[0].cmd_sts.rf_tv = 0; + return true; } -#endif static void prepare_ep_xfer(uint8_t rhport, uint8_t ep_id, uint16_t buf_offset, uint16_t total_bytes) { uint16_t nbytes; -- cgit v1.3.1 From 4bbb23545a91926b9372554f5a31294d0a279829 Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 9 Jul 2026 23:38:28 +0700 Subject: dcd(nrf5x): errata 199 DMA workaround + iso alloc/activate USBD drops tasks during EasyDMA without the 0x40027C1C latch (anomaly 199); matches the nrfx reference driver. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg --- src/portable/nordic/nrf5x/dcd_nrf5x.c | 100 +++++++++++++++++++--------------- 1 file changed, 56 insertions(+), 44 deletions(-) (limited to 'src') diff --git a/src/portable/nordic/nrf5x/dcd_nrf5x.c b/src/portable/nordic/nrf5x/dcd_nrf5x.c index 53d045d61..4c5ed012d 100644 --- a/src/portable/nordic/nrf5x/dcd_nrf5x.c +++ b/src/portable/nordic/nrf5x/dcd_nrf5x.c @@ -122,8 +122,22 @@ TU_ATTR_ALWAYS_INLINE static inline bool is_in_isr(void) { return (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) ? true : false; } +// Errata 199 "USBD cannot receive tasks during DMA": while an EasyDMA transfer is in progress the +// controller may drop an incoming SETUP/IN/OUT token (lost event -> stuck EP0, esp. under rapid +// back-to-back control transfers). The workaround latches an undocumented "DMA in progress" test +// register (0x40027C1C) so tokens are held instead. Gated on the anomaly being present (all +// nRF52840 revisions; absent on other nRF52 parts). Mirrors nrfx usbd_dma_pending_set/clear(). +#define NRF_USBD_ERRATA_199_REG (*((volatile uint32_t*) 0x40027C1CUL)) + // helper to start DMA static void start_dma(volatile uint32_t* reg_startep) { + // EP0STATUS / EP0RCVOUT take the EasyDMA slot but do not transfer data, so no ERRATA-199 latch. + const bool no_dma = (reg_startep == &NRF_USBD->TASKS_EP0STATUS) || (reg_startep == &NRF_USBD->TASKS_EP0RCVOUT); + + if (!no_dma && nrf52_errata_199()) { + NRF_USBD_ERRATA_199_REG = 0x00000082UL; + } + (*reg_startep) = 1; __ISB(); __DSB(); @@ -131,7 +145,7 @@ static void start_dma(volatile uint32_t* reg_startep) { // TASKS_EP0STATUS, TASKS_EP0RCVOUT seem to need EasyDMA to be available // However these don't trigger any DMA transfer and got ENDED event subsequently // Therefore dma_pending is corrected right away - if ((reg_startep == &NRF_USBD->TASKS_EP0STATUS) || (reg_startep == &NRF_USBD->TASKS_EP0RCVOUT)) { + if (no_dma) { atomic_flag_clear(&_dcd.dma_running); } } @@ -146,6 +160,10 @@ static void edpt_dma_start(volatile uint32_t* reg_startep) { // DMA is complete static void edpt_dma_end(void) { + // Clear the ERRATA-199 "DMA in progress" latch set in start_dma(). + if (nrf52_errata_199()) { + NRF_USBD_ERRATA_199_REG = 0x00000000UL; + } atomic_flag_clear(&_dcd.dma_running); } @@ -377,57 +395,51 @@ void dcd_edpt_close_all(uint8_t rhport) { dcd_int_enable(rhport); } -void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr) { - (void) rhport; - - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); - - if (epnum != EP_ISO_NUM) { - // CBI - if (dir == TUSB_DIR_OUT) { - NRF_USBD->INTENCLR = TU_BIT(USBD_INTEN_ENDEPOUT0_Pos + epnum); - NRF_USBD->EPOUTEN &= ~TU_BIT(epnum); - } else { - NRF_USBD->INTENCLR = TU_BIT(USBD_INTEN_ENDEPIN0_Pos + epnum); - NRF_USBD->EPINEN &= ~TU_BIT(epnum); - } - } else { - _dcd.xfer[EP_ISO_NUM][dir].mps = 0; - // ISO - if (dir == TUSB_DIR_OUT) { - NRF_USBD->INTENCLR = USBD_INTENCLR_ENDISOOUT_Msk; - NRF_USBD->EPOUTEN &= ~USBD_EPOUTEN_ISOOUT_Msk; - NRF_USBD->EVENTS_ENDISOOUT = 0; - } else { - NRF_USBD->INTENCLR = USBD_INTENCLR_ENDISOIN_Msk; - NRF_USBD->EPINEN &= ~USBD_EPINEN_ISOIN_Msk; - } - // One of the ISO endpoints closed, no need to split buffers any more. - NRF_USBD->ISOSPLIT = USBD_ISOSPLIT_SPLIT_OneDir; - // When both ISO endpoint are close there is no need for SOF any more. - if (_dcd.xfer[EP_ISO_NUM][TUSB_DIR_IN].mps + _dcd.xfer[EP_ISO_NUM][TUSB_DIR_OUT].mps == 0) - NRF_USBD->INTENCLR = USBD_INTENCLR_SOF_Msk; - } - _dcd.xfer[epnum][dir].started = false; - __ISB(); - __DSB(); -} - -#if 0 bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { (void)rhport; - (void)ep_addr; (void)largest_packet_size; - return false; + // nRF ISO endpoints are hardware-fixed to EP8 and use EasyDMA, so there is no packet buffer to + // pre-allocate here; the endpoint is enabled on dcd_edpt_iso_activate(). + TU_ASSERT(tu_edpt_number(ep_addr) == EP_ISO_NUM); + return true; } bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) { (void)rhport; - (void)desc_ep; - return false; + uint8_t const ep_addr = desc_ep->bEndpointAddress; + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + TU_ASSERT(epnum == EP_ISO_NUM); + + // A transfer armed before SET_INTERFACE survives to here (this port has no dcd close); usbd has + // just reset the endpoint's claim/busy state, so drop the stale descriptor too — otherwise the + // class's next arm trips TU_ASSERT(!xfer->started) in dcd_edpt_xfer(). + _dcd.xfer[epnum][dir].started = false; + _dcd.xfer[epnum][dir].data_received = false; + _dcd.xfer[epnum][dir].iso_in_transfer_ready = false; + + _dcd.xfer[epnum][dir].mps = tu_edpt_packet_size(desc_ep); + + if (dir == TUSB_DIR_OUT) { + // SPLIT ISO buffer when the ISO IN endpoint is already active. + if (_dcd.xfer[EP_ISO_NUM][TUSB_DIR_IN].mps) NRF_USBD->ISOSPLIT = USBD_ISOSPLIT_SPLIT_HalfIN; + NRF_USBD->EVENTS_ENDISOOUT = 0; + if ((NRF_USBD->INTEN & USBD_INTEN_SOF_Msk) == 0) NRF_USBD->EVENTS_SOF = 0; + NRF_USBD->INTENSET = USBD_INTENSET_ENDISOOUT_Msk | USBD_INTENSET_SOF_Msk; + NRF_USBD->EPOUTEN |= USBD_EPOUTEN_ISOOUT_Msk; + } else { + NRF_USBD->EVENTS_ENDISOIN = 0; + // SPLIT ISO buffer when the ISO OUT endpoint is already active. + if (_dcd.xfer[EP_ISO_NUM][TUSB_DIR_OUT].mps) NRF_USBD->ISOSPLIT = USBD_ISOSPLIT_SPLIT_HalfIN; + if ((NRF_USBD->INTEN & USBD_INTEN_SOF_Msk) == 0) NRF_USBD->EVENTS_SOF = 0; + NRF_USBD->INTENSET = USBD_INTENSET_ENDISOIN_Msk | USBD_INTENSET_SOF_Msk; + NRF_USBD->EPINEN |= USBD_EPINEN_ISOIN_Msk; + } + + __ISB(); + __DSB(); + return true; } -#endif bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t total_bytes, bool is_isr) { (void) rhport; -- cgit v1.3.1 From ad7acc849ab36c1bc2e560fcfac89bd136ec96b3 Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 9 Jul 2026 23:38:31 +0700 Subject: dcd(rp2040): re-issue in-flight transfer on clear-halt toggle reset Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg --- src/portable/raspberrypi/rp2040/dcd_rp2040.c | 43 ++++++++++++++++++++++++++-- src/portable/raspberrypi/rp2040/rp2040_usb.c | 9 ++++-- 2 files changed, 47 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c index 63097cd0a..a0d312b8f 100644 --- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c @@ -550,9 +550,46 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { if (epnum != 0) { struct hw_endpoint* ep = hw_endpoint_get(epnum, dir); - ep->next_pid = 0; // reset data toggle - io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir); - *buf_reg = 0; + + if (ep->state == EPSTATE_ACTIVE) { + // Clear-halt on an endpoint with an in-flight transfer is used as a data-toggle reset + // (e.g. usbtest case 29) rather than to recover from a real stall (a stall aborts the + // transfer, leaving the endpoint IDLE). Abort and re-issue the transfer with the toggle + // reset to DATA0 so it still completes and releases the usbd claim, instead of silently + // dropping it and starving the endpoint. Save the buffer/length before the abort clears them. + uint8_t* user_buf = ep->user_buf; + uint16_t remaining = ep->remaining_len; + const uint16_t xferred = ep->xferred_len; // bytes already moved on this submission + io_rw_32 *ep_reg = get_ep_ctrl(epnum, dir); + io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir); + // bufctrl_prepare16() subtracts each armed buffer's length from remaining_len when arming, + // for BOTH directions, before the host has drained (IN) or filled (OUT) it. The abort below + // discards those still-armed buffers, so rewind remaining_len by their lengths or the re-issue + // is short by 1-2 packets. IN additionally advances user_buf as packets are copied into DPRAM, + // so its pointer must rewind too; OUT copies out only on completion, so its pointer is intact. + const uint32_t bc = *buf_reg; + uint16_t staged = 0; + if (bc & USB_BUF_CTRL_AVAIL) { + staged = (uint16_t)(bc & USB_BUF_CTRL_LEN_MASK); + } + if ((bc >> 16) & USB_BUF_CTRL_AVAIL) { + staged = (uint16_t)(staged + ((bc >> 16) & USB_BUF_CTRL_LEN_MASK)); + } + remaining = (uint16_t)(remaining + staged); + if (dir == TUSB_DIR_IN) { + user_buf -= staged; + } + hw_endpoint_abort_xfer(ep); // safe abort (handles RP2040-E2), resets ep transfer state + ep->next_pid = 0; // DATA0 + rp2usb_xfer_start(ep, ep_reg, buf_reg, user_buf, NULL, remaining); + // rp2usb_xfer_start() zeroes xferred_len; add back what the aborted transfer already moved so + // the eventual completion reports the full length, not just the post-clear-halt remainder. + ep->xferred_len += xferred; + } else { + ep->next_pid = 0; // reset data toggle + io_rw_32 *buf_reg = get_buf_ctrl(epnum, dir); + *buf_reg = 0; // clear the stall response + } } } diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index e4eb0184e..5421b9b2b 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -176,10 +176,15 @@ void __tusb_irq_path_func(rp2usb_buffer_start)(hw_endpoint_t *ep, io_rw_32 *ep_r // Note: device EP0 does not have an endpoint control register if (ep_reg != NULL) { uint32_t ep_ctrl = *ep_reg; + // Isochronous endpoints get a single DPRAM buffer (hw_endpoint_open only double-sizes BULK), so + // they must never be double-buffered here even when a transfer spans multiple packets, or buffer + // 1 (at dpram_buf+64) would spill into the next endpoint's DPRAM. (Never true for BULK, so the + // double-buffered bulk path is unaffected.) + const bool is_iso = (((ep_ctrl >> EP_CTRL_BUFFER_TYPE_LSB) & 0x3u) == TUSB_XFER_ISOCHRONOUS); #if CFG_TUH_ENABLED - const bool force_single = (rp2usb_is_host_mode() && ep->interrupt_num > 0); + const bool force_single = is_iso || (rp2usb_is_host_mode() && ep->interrupt_num > 0); #else - const bool force_single = false; + const bool force_single = is_iso; #endif if (ep->remaining_len && !force_single) { -- cgit v1.3.1 From 0464636878851a26b9995972a90aefe3825d043b Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 9 Jul 2026 23:38:35 +0700 Subject: dcd(fsdev): don't disarm an armed endpoint on clear-halt toggle reset Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg --- src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c b/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c index aecd689b3..ba05818b6 100644 --- a/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c +++ b/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c @@ -835,7 +835,17 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { ep_reg &= U_EPREG_MASK | EP_STAT_MASK(dir) | EP_DTOG_MASK(dir); if (!ep_is_iso(ep_reg)) { - ep_change_status(&ep_reg, dir, EP_STAT_NAK); + // Only knock a genuinely STALLED endpoint down to NAK (the class then re-arms it). If the + // endpoint is armed (VALID) - e.g. a clear-halt used purely to reset the data toggle, as in + // usbtest case 29 - leave STAT untouched so the in-flight transfer isn't disarmed with no + // completion, which would leak the usbd claim and starve the endpoint. Masking the STAT bits + // to 0 writes no toggle, so an armed/idle endpoint keeps its current status. + const uint8_t stat_pos = (uint8_t) (U_EPTX_STAT_Pos + (dir == TUSB_DIR_IN ? 0u : 8u)); + if (((ep_reg >> stat_pos) & 0x3u) == EP_STAT_STALL) { + ep_change_status(&ep_reg, dir, EP_STAT_NAK); + } else { + ep_reg &= ~EP_STAT_MASK(dir); + } } ep_change_dtog(&ep_reg, dir, 0); // Reset to DATA0 ep_write(ep_idx, ep_reg, true); -- cgit v1.3.1 From 3fc60eafb3e7232e402c69d5ebd14c6de15034af Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 9 Jul 2026 23:38:38 +0700 Subject: dcd(musb): flush TX FIFO on halt; don't load a disarmed pipe Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg --- src/portable/mentor/musb/dcd_musb.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'src') diff --git a/src/portable/mentor/musb/dcd_musb.c b/src/portable/mentor/musb/dcd_musb.c index 1ebd1fe02..17993f23a 100644 --- a/src/portable/mentor/musb/dcd_musb.c +++ b/src/portable/mentor/musb/dcd_musb.c @@ -292,6 +292,12 @@ static void process_epin_isr(uint8_t rhport, musb_regs_t *musb_regs, uint8_t epn } pipe_state_t* pipe = pipe_get(epnum, TUSB_DIR_IN); + // No active transfer: a halt/abort disarmed the pipe (armed=false) but may leave remaining>0. + // Do not keep loading the aborted transfer — that would re-fill the just-flushed FIFO and the + // next (re-armed) transfer's data would stack on top (host sees an oversized packet -> babble). + if (!pipe->armed) { + return; + } if (pipe->remaining > 0) { pipe_write(musb_regs, pipe, epnum); } else { @@ -910,6 +916,10 @@ void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { } else { const tusb_dir_t ep_dir = tu_edpt_dir(ep_addr); const uint8_t is_rx = (ep_dir == TUSB_DIR_OUT ? 1u : 0u); + // A halt aborts the transfer: flush staged FIFO packet(s) before stalling, else leftover TX data + // concatenates with the next transfer after un-halt -> host sees an oversized packet (babble). + // FLUSH must precede SEND_STALL, which clears the TXRDY that hwfifo_flush() gates on. + hwfifo_flush(musb_regs, epn, is_rx, false); ep_csr->maxp_csr[is_rx].csrl = MUSB_CSRL_SEND_STALL(is_rx); pipe_state_t* pipe = pipe_get(epn, ep_dir); pipe->armed = false; -- cgit v1.3.1 From 99044894aa4d91a4dadbbf865b1a945bab4ed1e3 Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 9 Jul 2026 23:38:41 +0700 Subject: dcd(ch32-usbhs): re-queue the pending OUT read on clear-halt Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg --- src/portable/wch/dcd_ch32_usbhs.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/portable/wch/dcd_ch32_usbhs.c b/src/portable/wch/dcd_ch32_usbhs.c index 0c154f5ce..577f86582 100644 --- a/src/portable/wch/dcd_ch32_usbhs.c +++ b/src/portable/wch/dcd_ch32_usbhs.c @@ -348,8 +348,16 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { const tusb_dir_t dir = tu_edpt_dir(ep_addr); if (dir == TUSB_DIR_OUT) { - EP_RX_CTRL(ep_num) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0; - ep_data_tog[ep_num][TUSB_DIR_OUT] = false; + ep_data_tog[ep_num][TUSB_DIR_OUT] = false; // clear-halt resets the toggle to DATA0 + xfer_ctl_t *xfer = XFER_CTL_BASE(ep_num, TUSB_DIR_OUT); + if (xfer->valid) { + // A receive is still armed (the class driver considers it submitted and won't re-arm it); + // re-queue it (ACK/NYET) instead of leaving it NAKing, or the endpoint NAKs forever after + // clear-halt (usbtest toggle test 29 clears the halt on an armed bulk-OUT pipe). + queue_out_packet(ep_num, xfer); + } else { + EP_RX_CTRL(ep_num) = USBHS_EP_R_RES_NAK | USBHS_EP_R_TOG_0; + } } else { EP_TX_CTRL(ep_num) = USBHS_EP_T_RES_NAK | USBHS_EP_T_TOG_0; ep_data_tog[ep_num][TUSB_DIR_IN] = false; -- cgit v1.3.1 From c97c0a12bc5ab8e79aa3db0a777e0219692b5751 Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 9 Jul 2026 23:38:45 +0700 Subject: dcd(ch32-usbfs): isochronous support Double-buffered iso, EP3 1023-byte packets on V20x/V30x (10-bit R16_UEP3_T_LEN), CH583 and V103 enabled at 64 B. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg --- src/portable/wch/ch32_usbfs_reg.h | 8 ++++ src/portable/wch/dcd_ch32_usbfs.c | 97 ++++++++++++++++++++++++--------------- 2 files changed, 69 insertions(+), 36 deletions(-) (limited to 'src') diff --git a/src/portable/wch/ch32_usbfs_reg.h b/src/portable/wch/ch32_usbfs_reg.h index 8bac103fe..5b037281f 100644 --- a/src/portable/wch/ch32_usbfs_reg.h +++ b/src/portable/wch/ch32_usbfs_reg.h @@ -179,6 +179,14 @@ #endif #endif +// CH32V20x/V30x/F20x USBFS gives endpoint 3 a 1023-byte isochronous packet (CH32FV2x_V3xRM ch23: +// every endpoint is 64 B except EP3 = 1023 B, from EP3's 10-bit R16_UEP3_T_LEN field plus a single +// contiguous >=1023 B DMA buffer — NOT double-buffering, which only yields 2x64 B). +// CH32V103/X035/CH58x cap every endpoint at 64 B. dcd_ch32_usbfs.c reads this to size EP3's buffer. +#if CFG_TUSB_MCU == OPT_MCU_CH32V20X || CFG_TUSB_MCU == OPT_MCU_CH32V307 || CFG_TUSB_MCU == OPT_MCU_CH32F20X + #define CH32_USBFS_EP3_1023_BUFSIZE 1 +#endif + #ifdef __GNUC__ #pragma GCC diagnostic pop #endif diff --git a/src/portable/wch/dcd_ch32_usbfs.c b/src/portable/wch/dcd_ch32_usbfs.c index a6458748a..09aa53490 100644 --- a/src/portable/wch/dcd_ch32_usbfs.c +++ b/src/portable/wch/dcd_ch32_usbfs.c @@ -16,6 +16,17 @@ /* private defines */ #define EP_MAX (8) + // EP3 IN buffer size. CH32V20x/V30x/F20x USBFS support full-speed iso packets up to 1023 B on + // endpoint 3 (every other endpoint is 64 B); those parts set CH32_USBFS_EP3_1023_BUFSIZE in + // ch32_usbfs_reg.h. V103/X035/CH58x cap every endpoint at 64 B. Overridable per project. + #ifndef CFG_TUD_WCH_USBFS_EP3_BUFSIZE + #ifdef CH32_USBFS_EP3_1023_BUFSIZE + #define CFG_TUD_WCH_USBFS_EP3_BUFSIZE 1023 + #else + #define CFG_TUD_WCH_USBFS_EP3_BUFSIZE 64 + #endif + #endif + // Struct-based EP register access (uniform layout). CH58X has a different register map and // defines EP_DMA/EP_TX_LEN/EP_CTRL itself in ch32_usbfs_reg.h. #if CFG_TUSB_MCU == OPT_MCU_CH583 @@ -107,7 +118,7 @@ struct usb_xfer { static struct { bool ep0_tog; - bool isochronous[EP_MAX]; + bool isochronous[EP_MAX][2]; // per [ep][dir]: an ep number may be iso in one direction struct usb_xfer xfer[EP_MAX][2]; #ifdef CH32_USBFS_EP4_SHARES_EP0 // CH58X buffers laid out by hand so EP0/EP4 don't burn two unused buffer[] slots. EP0 and EP4 @@ -123,21 +134,23 @@ static struct { TU_ATTR_ALIGNED(4) uint8_t ep6_buffer[2][64]; TU_ATTR_ALIGNED(4) uint8_t ep7_buffer[2][64]; #else + // Every endpoint gets a 64-byte OUT + 64-byte IN buffer. TU_ATTR_ALIGNED(4) uint8_t buffer[EP_MAX][2][64]; - // EP3 IN gets an enlarged buffer for full-speed isochronous (packets up to 1023 B). + #if CFG_TUD_WCH_USBFS_EP3_BUFSIZE > 64 + // ...except EP3, which supports full-speed iso packets up to 1023 B on CH32V20x/V30x/F20x, so its + // IN buffer is enlarged (OUT stays 64 B; an OUT transfer >64 B on EP3 would overwrite queued IN). TU_ATTR_ALIGNED(4) struct { - // OUT transfers >64 bytes will overwrite queued IN data! uint8_t out[64]; - uint8_t in[1023]; + uint8_t in[CFG_TUD_WCH_USBFS_EP3_BUFSIZE]; uint8_t pad; } ep3_buffer; + #endif #endif } data; // DMA / copy buffer pointers per endpoint. The WCH USBFS buffer holds OUT (RX) at offset 0 and -// IN (TX) at +64; EP0 is half-duplex and reuses its OUT chunk for IN; EP3 has an enlarged IN -// buffer for throughput. On CH58X, EP0/EP4 share ep0_ep4_buffer and the regular endpoints use -// their own named buffer (see the struct above). +// IN (TX) at +64; EP0 is half-duplex and reuses its OUT chunk for IN. On CH58X, EP0/EP4 share +// ep0_ep4_buffer and the regular endpoints use their own named buffer (see the struct above). #ifdef CH32_USBFS_EP4_SHARES_EP0 // OUT base of the regular CH58X endpoints (EP1/2/3/5/6/7; EP0/EP4 share ep0_ep4_buffer). static inline uint8_t* ch58x_ep_buffer(uint8_t ep) { @@ -157,7 +170,9 @@ static inline uint32_t ep_dma_addr(uint8_t ep) { if (ep == 0 || ep == 4) { return (uint32_t) &data.ep0_ep4_buffer[0]; } // EP4 shares EP0's DMA return (uint32_t) ch58x_ep_buffer(ep); #else - if (ep == 3) { return (uint32_t) &data.ep3_buffer.out[0]; } + #if CFG_TUD_WCH_USBFS_EP3_BUFSIZE > 64 + if (ep == 3) { return (uint32_t) &data.ep3_buffer.out[0]; } // EP3 has an enlarged IN buffer + #endif return (uint32_t) &data.buffer[ep][0]; #endif } @@ -168,7 +183,9 @@ static inline uint8_t* ep_out_buf(uint8_t ep) { if (ep == 4) { return &data.ep0_ep4_buffer[64]; } return ch58x_ep_buffer(ep); #else + #if CFG_TUD_WCH_USBFS_EP3_BUFSIZE > 64 if (ep == 3) { return data.ep3_buffer.out; } + #endif return data.buffer[ep][TUSB_DIR_OUT]; #endif } @@ -180,7 +197,9 @@ static inline uint8_t* ep_in_buf(uint8_t ep) { return ch58x_ep_buffer(ep) + 64; // IN at +64 within the endpoint's 128-byte buffer #else if (ep == 0) { return data.buffer[0][TUSB_DIR_OUT]; } // EP0 half-duplex: IN reuses OUT chunk - if (ep == 3) { return data.ep3_buffer.in; } + #if CFG_TUD_WCH_USBFS_EP3_BUFSIZE > 64 + if (ep == 3) { return data.ep3_buffer.in; } // enlarged IN buffer for full-speed iso + #endif return data.buffer[ep][TUSB_DIR_IN]; #endif } @@ -202,9 +221,8 @@ static void update_in(uint8_t rhport, uint8_t ep, bool force) { if (force || xfer->len) { size_t len = TU_MIN(xfer->max_size, xfer->len); #if CFG_TUSB_MCU == OPT_MCU_CH583 - // Every CH58x endpoint buffer is 64 bytes. Isochronous (which would push max_size up to 1023) - // is refused in dcd_edpt_iso_alloc(), but some classes (e.g. video) ignore that result, so cap - // the copy here to guarantee we never write past the buffer into a neighbouring endpoint's. + // Every CH58x endpoint buffer is 64 bytes; cap the copy so an iso mps a class mistakenly set + // larger can't write past the buffer into a neighbouring endpoint's. len = TU_MIN(len, 64u); #endif memcpy(ep_in_buf(ep), xfer->buffer, len); @@ -216,7 +234,7 @@ static void update_in(uint8_t rhport, uint8_t ep, bool force) { if (ep == 0) { ep_tx_ctrl_set(0, USBFS_EP_T_RES_ACK | (data.ep0_tog ? USBFS_EP_T_TOG : 0)); data.ep0_tog = !data.ep0_tog; - } else if (data.isochronous[ep]) { + } else if (data.isochronous[ep][TUSB_DIR_IN]) { ep_tx_set_response(ep, USBFS_EP_T_RES_NYET); } else { ep_tx_set_response(ep, USBFS_EP_T_RES_ACK); @@ -225,7 +243,7 @@ static void update_in(uint8_t rhport, uint8_t ep, bool force) { xfer->valid = false; if (ep == 0) { ep_tx_ctrl_set(0, USBFS_EP_T_RES_NAK | (data.ep0_tog ? USBFS_EP_T_TOG : 0)); - } else if (!data.isochronous[ep]) { + } else if (!data.isochronous[ep][TUSB_DIR_IN]) { ep_tx_set_response(ep, USBFS_EP_T_RES_NAK); } dcd_event_xfer_complete(rhport, ep | TUSB_DIR_IN_MASK, xfer->processed_len, XFER_RESULT_SUCCESS, true); @@ -254,7 +272,7 @@ static void update_out(uint8_t rhport, uint8_t ep, size_t rx_len) { ep_rx_set_response(0, USBFS_EP_R_RES_NAK); } else { uint8_t rx_res = - data.isochronous[ep] ? USBFS_EP_R_RES_NYET : (xfer->valid ? USBFS_EP_R_RES_ACK : USBFS_EP_R_RES_NAK); + data.isochronous[ep][TUSB_DIR_OUT] ? USBFS_EP_R_RES_NYET : (xfer->valid ? USBFS_EP_R_RES_ACK : USBFS_EP_R_RES_NAK); ep_rx_set_response(ep, rx_res); } } @@ -319,12 +337,14 @@ void dcd_int_handler(uint8_t rhport) { // Drop an OUT packet whose data toggle doesn't match what we expect -- a host retransmit // after a lost ACK, or a host that doesn't alternate DATA0/DATA1. The hardware auto-toggle // does not reject these on its own, so the check is needed on every variant. EP0 keeps its - // own toggle via the SETUP/status flow and is exempt. - if (ep != 0 && !(int_st & USBFS_INT_ST_TOG_OK)) { break; } + // own toggle via the SETUP/status flow and is exempt; isochronous is DATA0-only (no toggle), + // so its packets must not be toggle-checked. + if (ep != 0 && !data.isochronous[ep][TUSB_DIR_OUT] && !(int_st & USBFS_INT_ST_TOG_OK)) { break; } #ifdef CH32_USBFS_EP_MANUAL_TOG // CH58x has no hardware auto-toggle: advance the expected RX toggle after each accepted packet // (EP0 included -- it also has no auto-toggle and a control-OUT data stage can span packets). - EP_CTRL(ep) ^= USBFS_EPC_R_TOG; + // Iso endpoints are DATA0-only, so leave them alone (matches the PID_IN path). + if (!data.isochronous[ep][TUSB_DIR_OUT]) { EP_CTRL(ep) ^= USBFS_EPC_R_TOG; } #endif update_out(rhport, ep, rx_len); break; @@ -333,7 +353,8 @@ void dcd_int_handler(uint8_t rhport) { case PID_IN: #ifdef CH32_USBFS_EP_MANUAL_TOG // Manual toggle: flip the TX toggle after each ACK'd IN packet (EP0 manages its own). - if (ep != 0) { EP_CTRL(ep) ^= USBFS_EPC_T_TOG; } + // Isochronous transfers are DATA0-only (no toggle), so leave iso endpoints alone. + if (ep != 0 && !data.isochronous[ep][TUSB_DIR_IN]) { EP_CTRL(ep) ^= USBFS_EPC_T_TOG; } #endif update_in(rhport, ep, false); break; @@ -443,6 +464,7 @@ bool dcd_edpt_open(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) { uint8_t dir = tu_edpt_dir(desc_ep->bEndpointAddress); TU_ASSERT(ep < EP_MAX); + data.isochronous[ep][dir] = false; // (re)opening as a non-iso endpoint clears any stale iso flag data.xfer[ep][dir].max_size = tu_edpt_packet_size(desc_ep); if (ep != 0) { @@ -464,31 +486,28 @@ void dcd_edpt_close_all(uint8_t rhport) { bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { (void)rhport; - (void)ep_addr; - (void)largest_packet_size; -#if CFG_TUSB_MCU == OPT_MCU_CH583 - // No isochronous support on CH58x: its 8-bit T_LEN caps a packet at 255B and the endpoints use - // plain 64-byte buffers, so accepting an iso max_size (up to 1023) would let update_in()/ - // update_out() run off the end of the buffer into neighbouring ones. Refuse it outright. - return false; -#else uint8_t ep = tu_edpt_number(ep_addr); uint8_t dir = tu_edpt_dir(ep_addr); + TU_ASSERT(ep < EP_MAX); + + // Endpoint buffers are 64 B, except EP3 IN which is enlarged for full-speed iso on the parts that + // support 1023-byte EP3 packets (CH32V20x/V30x/F20x; CFG_TUD_WCH_USBFS_EP3_BUFSIZE). Reject a + // larger mps rather than running off the end into the neighbouring endpoint's memory. + uint16_t max_packet = 64; +#if CFG_TUD_WCH_USBFS_EP3_BUFSIZE > 64 + if (ep == 3 && dir == TUSB_DIR_IN) { max_packet = CFG_TUD_WCH_USBFS_EP3_BUFSIZE; } +#endif + TU_VERIFY(largest_packet_size <= max_packet); - data.isochronous[ep] = true; + data.isochronous[ep][dir] = true; data.xfer[ep][dir].max_size = largest_packet_size; return true; -#endif } bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) { (void)rhport; (void)desc_ep; -#if CFG_TUSB_MCU == OPT_MCU_CH583 - return false; // CH58x has no isochronous support (see dcd_edpt_iso_alloc) -#else return true; -#endif } bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) { @@ -510,7 +529,7 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to if (dir == TUSB_DIR_IN) { update_in(rhport, ep, true); } else { - uint8_t rx_res = data.isochronous[ep] ? USBFS_EP_R_RES_NYET : USBFS_EP_R_RES_ACK; + uint8_t rx_res = data.isochronous[ep][TUSB_DIR_OUT] ? USBFS_EP_R_RES_NYET : USBFS_EP_R_RES_ACK; ep_rx_set_response(ep, rx_res); } dcd_int_enable(rhport); @@ -546,9 +565,15 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { ep_rx_ctrl_set(0, USBFS_EP_R_RES_ACK); } } else { - // clear-stall resets the toggle to DATA0 (USB spec); manual-toggle parts then re-sync via ISR + // clear-stall resets the toggle to DATA0 (USB spec); manual-toggle parts then re-sync via ISR. + // Preserve an in-flight receive: if a read is still armed (the class driver considers it + // submitted and won't re-arm), fall back to ACK, not NAK, or the endpoint NAKs forever and the + // host times out (usbtest toggle test 29 clears the halt between bulk writes on an armed EP). if (dir == TUSB_DIR_OUT) { - ep_rx_ctrl_set(ep, EP_R_AUTO_TOG | USBFS_EP_R_RES_NAK); + uint8_t res = data.xfer[ep][TUSB_DIR_OUT].valid + ? (data.isochronous[ep][TUSB_DIR_OUT] ? USBFS_EP_R_RES_NYET : USBFS_EP_R_RES_ACK) + : USBFS_EP_R_RES_NAK; + ep_rx_ctrl_set(ep, EP_R_AUTO_TOG | res); } else { ep_tx_ctrl_set(ep, EP_T_AUTO_TOG | USBFS_EP_T_RES_NAK); } -- cgit v1.3.1 From 411fc4127f17afe81c5a443008e883dd58390238 Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 9 Jul 2026 23:38:49 +0700 Subject: audio: drop stale EP-busy note in audiod_set_interface Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg --- src/class/audio/audio_device.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'src') diff --git a/src/class/audio/audio_device.c b/src/class/audio/audio_device.c index 12e82190e..94881521a 100644 --- a/src/class/audio/audio_device.c +++ b/src/class/audio/audio_device.c @@ -1161,9 +1161,6 @@ static bool audiod_set_interface(uint8_t rhport, tusb_control_request_t const *p is_feedback_ep = (desc_ep->bmAttributes.usage == 1); } - //TODO: We need to set EP non busy since this is not taken care of right now in ep_close() - THIS IS A WORKAROUND! - usbd_edpt_clear_stall(rhport, ep_addr); - #if CFG_TUD_AUDIO_ENABLE_EP_IN // For data or data with implicit feedback IN EP if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN && is_data_ep) -- cgit v1.3.1 From 628e0e2998bc4a7363c320fc8ff1b90b7ded3009 Mon Sep 17 00:00:00 2001 From: hathach Date: Sun, 12 Jul 2026 00:10:07 +0700 Subject: dcd(ip3511): clear Active directly on stall/iso-activate, keep EPSKIP for reopen EPSKIP raises a transfer completion, so using it on the stall path let the class re-arm the endpoint and Active+Stall never actually stalled (usbtest case 13); write bare Active=0 instead, and retire skipped transfers on endpoint reopen where the completion is wanted. Verified: usbtest 30/30 on lpcxpresso11u37. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg --- src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c | 39 ++++++++++++++++------------ 1 file changed, 23 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c b/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c index 3e9091589..d5b03e4b1 100644 --- a/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c +++ b/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c @@ -341,10 +341,16 @@ void dcd_sof_enable(uint8_t rhport, bool en) //--------------------------------------------------------------------+ // DCD Endpoint Port //--------------------------------------------------------------------+ -// Retire a still-armed (Active) endpoint the sanctioned way before its command/status entry is -// rewritten (halt, reopen, altsetting switch). UM11126 §41.7.6/§41.8.3: write EPSKIP and wait for -// hardware to clear the bit, then Active is safe to clear — a bare Active=0 can race a mid-packet -// buffer. Bounded: hardware clears EPSKIP within a (micro)frame; the guard only prevents a hang. +// Retire a still-armed (Active) endpoint before reconfiguring it (reopen across SET_INTERFACE). +// UM11126 §41.7.6/§41.8.3: write EPSKIP and wait for hardware to clear the bit, then Active is +// safe to clear. EPSKIP raises the endpoint interrupt as it clears Active, delivered as a +// (partial) transfer completion. Here that is sanctioned — usbd_edpt_close() documents "in +// progress transfers may be delivered after this call", and that completion is what clears the +// stale usbd busy flag (ISO_ALLOC close is a no-op) so the class can re-arm the reopened +// endpoint. NOT for the stall/iso-activate paths: there the class re-arms from the completion +// callback and the endpoint ends up Active+Stall, which never sends a STALL handshake (usbtest +// case 13 regression on LPC11u37) — those paths must clear Active directly instead. +// Bounded: hardware clears EPSKIP within a (micro)frame. static void edpt_skip_active(uint8_t rhport, uint8_t ep_id) { ep_cmd_sts_t* ep_cs = get_ep_cs(ep_id); if ( ep_cs[0].cmd_sts.active || ep_cs[1].cmd_sts.active ) { @@ -358,13 +364,14 @@ static void edpt_skip_active(uint8_t rhport, uint8_t ep_id) { void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { + (void) rhport; // TODO cannot able to STALL Control OUT endpoint !!!!! FIXME try some walk-around uint8_t const ep_id = ep_addr2id(ep_addr); - // Retire any armed buffer before setting Stall: the hardware services an armed (Active) buffer - // instead of returning STALL, so a halt requested while a transfer is queued would not actually - // stall the endpoint (usbtest case 13), and Active+Stall must not both be set. - edpt_skip_active(rhport, ep_id); - _dcd.ep[ep_id][0].cmd_sts.stall = 1; + // Clear Active directly before setting Stall (no EPSKIP — see edpt_skip_active): the hardware + // services an armed buffer instead of returning STALL, so a halt requested while a transfer is + // queued would not actually stall the endpoint (usbtest case 13). + _dcd.ep[ep_id][0].cmd_sts.active = 0; + _dcd.ep[ep_id][0].cmd_sts.stall = 1; } void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) @@ -448,15 +455,15 @@ bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet } bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) { - // (Re)activate on altsetting selection: retire a buffer still armed from the previous altsetting - // (the hardware keeps servicing an Active buffer across SET_INTERFACE, fighting the class's fresh - // transfer), clear stall and reset the data toggle. The class re-arms via dcd_edpt_xfer(). + // (Re)activate on altsetting selection: abort a transfer still armed from the previous + // altsetting (the hardware keeps servicing an Active buffer across SET_INTERFACE, fighting the + // fresh transfer the class queues), clear stall and reset the data toggle. Direct Active=0, not + // EPSKIP (see edpt_skip_active). The class re-arms via dcd_edpt_xfer(). uint8_t ep_id = ep_addr2id(desc_ep->bEndpointAddress); ep_cmd_sts_t* ep_cs = get_ep_cs(ep_id); - edpt_skip_active(rhport, ep_id); - ep_cs[0].cmd_sts.stall = 0; - ep_cs[0].cmd_sts.toggle_reset = 1; - ep_cs[0].cmd_sts.rf_tv = 0; + ep_cs[0].cmd_sts.active = 0; + ep_cs[1].cmd_sts.active = 0; + dcd_edpt_clear_stall(rhport, desc_ep->bEndpointAddress); return true; } -- cgit v1.3.1 From 23242accfff0cfde25cc6c089d2e8dcd4d5560e4 Mon Sep 17 00:00:00 2001 From: hathach Date: Sun, 12 Jul 2026 00:10:10 +0700 Subject: dcd(ch32_usbfs): reset stale transfer state in dcd_edpt_iso_activate The no-op activate left a transfer armed before SET_INTERFACE valid in data.xfer, letting the ISR complete it against the old buffer. Drop the descriptor and NAK the endpoint (mirrors the nrf5x fix). Verified: usbtest 30/30 on ch32v103r, nanoch32v203, ch582m_evt. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg --- src/portable/wch/dcd_ch32_usbfs.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/portable/wch/dcd_ch32_usbfs.c b/src/portable/wch/dcd_ch32_usbfs.c index 09aa53490..ec521224e 100644 --- a/src/portable/wch/dcd_ch32_usbfs.c +++ b/src/portable/wch/dcd_ch32_usbfs.c @@ -506,7 +506,17 @@ bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) { (void)rhport; - (void)desc_ep; + const uint8_t ep = tu_edpt_number(desc_ep->bEndpointAddress); + const uint8_t dir = tu_edpt_dir(desc_ep->bEndpointAddress); + + // a transfer armed before SET_INTERFACE survives to here (no dcd close on this port): drop the + // stale descriptor and NAK the endpoint so the ISR can't complete it against the old buffer + data.xfer[ep][dir].valid = false; + if (dir == TUSB_DIR_IN) { + ep_tx_set_response(ep, USBFS_EP_T_RES_NAK); + } else { + ep_rx_set_response(ep, USBFS_EP_R_RES_NAK); + } return true; } -- cgit v1.3.1 From 24f8bce0bc4a07a69f242ff1e790da90719e984d Mon Sep 17 00:00:00 2001 From: hathach Date: Mon, 13 Jul 2026 15:30:44 +0700 Subject: rusb2: EP0 OUT reliability, HS UTMI PHY power-up, FS-only build support - EP0 OUT: park a back-to-back data-stage packet the DCP accepted before PID could go NAK and deliver it into the next armed chunk; flow-control the single-buffer control pipe between chunks (usbtest ctrl_out corruption); discard a packet parked while an OUT pipe was halted so BOT reset recovery's fresh CBW read can't receive stale WRITE data - HS UTMI PHY power-up per the FSP sequence, shared by dcd/hcd: CLKSEL programmed from the board XTAL (EK-RA8M1 runs 20 MHz; the 24 MHz reset default never locks) while DIRPD holds the PHY down, then timed release - hw/bsp(ra8m1_ek): fix U60CK divider macro - BSP_CFG_U60CK_DIV used the generic USB_CLOCK_DIV_8 encoding (7), which USB60CKDIVCR rejects, leaving the USBHS link domain at 480 MHz; the USB60-specific BSP_CLOCKS_USB60_CLOCK_DIV_8 (4) sticks and yields the required 60 MHz from PLL1P - support FS-only builds on the high-speed port: gate SYSCFG.HSE on TUD_OPT_HIGH_SPEED (RHPORT_DEVICE_SPEED=OPT_MODE_FULL_SPEED was a silent no-op) and always compile both hwfifo access widths - the FIFO width belongs to the module, not the link speed (FS builds corrupted odd-length tails: 16-bit access against MBW-32) - iso activate: reset stale pipe bookkeeping so a BRDY firing before the class re-arms can't replay a pre-SET_INTERFACE transfer; write PIPEBUF after PIPESEL selects the pipe (PIPESEL-windowed register) - clear-halt: re-assert BUF on a still-armed OUT pipe (usbtest case 29) - bound the D0FIFO ready spin so an undrained double-buffered IN pipe can't freeze the stack with the IRQ masked - usbtest example: cap interrupt mps at 64 on RUSB2 high speed (pipes 6-9 have a fixed 64-byte buffer, RA6M5 UM 29.1) Verified: usbtest 30/30 on ra6m5_ek (HS), ra4m1_ek (FS) and ra8m1_ek (FS-forced build on the HS port). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg --- examples/device/usbtest/src/usb_descriptors.h | 8 ++- hw/bsp/ra/boards/ra8m1_ek/ra_gen/bsp_clock_cfg.h | 5 +- src/portable/renesas/rusb2/dcd_rusb2.c | 90 ++++++++++++++++-------- src/portable/renesas/rusb2/hcd_rusb2.c | 8 +-- src/portable/renesas/rusb2/rusb2_ra.h | 42 +++++++++++ src/tusb_option.h | 2 +- 6 files changed, 116 insertions(+), 39 deletions(-) (limited to 'src') diff --git a/examples/device/usbtest/src/usb_descriptors.h b/examples/device/usbtest/src/usb_descriptors.h index 8a033eca9..61b931bd0 100644 --- a/examples/device/usbtest/src/usb_descriptors.h +++ b/examples/device/usbtest/src/usb_descriptors.h @@ -57,7 +57,13 @@ #define USBTEST_INT_EP_MPS_FS 64 #define USBTEST_ISO_EP_MPS_FS 128 #endif -#define USBTEST_INT_EP_MPS_HS 512 +// RUSB2 (Renesas RA) interrupt pipes 6-9 have a fixed 64-byte single buffer at any speed +// (RA6M5 UM R01UH0891 sec 29.1: "Pipes 6 to 9: Interrupt transfer with 64-byte single buffer"). +#if TU_CHECK_MCU(OPT_MCU_RAXXX) + #define USBTEST_INT_EP_MPS_HS 64 +#else + #define USBTEST_INT_EP_MPS_HS 512 +#endif #define USBTEST_ISO_EP_MPS_HS 512 // Compile-time capability maximum: sizes the source buffers / vendor epbufs for the largest diff --git a/hw/bsp/ra/boards/ra8m1_ek/ra_gen/bsp_clock_cfg.h b/hw/bsp/ra/boards/ra8m1_ek/ra_gen/bsp_clock_cfg.h index f2f1ae0c9..25638f02b 100644 --- a/hw/bsp/ra/boards/ra8m1_ek/ra_gen/bsp_clock_cfg.h +++ b/hw/bsp/ra/boards/ra8m1_ek/ra_gen/bsp_clock_cfg.h @@ -51,6 +51,9 @@ #define BSP_CFG_CANFDCLK_DIV (BSP_CLOCKS_CANFD_CLOCK_DIV_8) /* CANFDCLK Div /8 */ #define BSP_CFG_I3CCLK_DIV (BSP_CLOCKS_I3C_CLOCK_DIV_3) /* I3CCLK Div /3 */ #define BSP_CFG_UCK_DIV (BSP_CLOCKS_USB_CLOCK_DIV_5) /* UCK Div /5 */ -#define BSP_CFG_U60CK_DIV (BSP_CLOCKS_USB_CLOCK_DIV_8) /* U60CK Div /8 */ +/* U60CK Div /8: PLL1P 480 MHz -> 60 MHz. Hand-fixed: Smart Configurator emitted the USB_ macro + * namespace (BSP_CLOCKS_USB_CLOCK_DIV_8 = 7, rejected by USB60CKDIVCR -> link clock ran at + * 480 MHz); configuration.xml already says u60ck.div.8, so keep the USB60_ macro if regenerating. */ +#define BSP_CFG_U60CK_DIV (BSP_CLOCKS_USB60_CLOCK_DIV_8) #define BSP_CFG_OCTA_DIV (BSP_CLOCKS_OCTA_CLOCK_DIV_4) /* OCTASPICLK Div /4 */ #endif /* BSP_CLOCK_CFG_H_ */ diff --git a/src/portable/renesas/rusb2/dcd_rusb2.c b/src/portable/renesas/rusb2/dcd_rusb2.c index 5e42f63f5..c93bab05e 100644 --- a/src/portable/renesas/rusb2/dcd_rusb2.c +++ b/src/portable/renesas/rusb2/dcd_rusb2.c @@ -43,6 +43,7 @@ typedef struct static dcd_data_t _dcd; + //--------------------------------------------------------------------+ // INTERNAL OBJECT & FUNCTION DECLARATION //--------------------------------------------------------------------+ @@ -190,6 +191,15 @@ static bool pipe0_xfer_out(rusb2_reg_t *rusb) { pipe_state_t *pipe = &_dcd.pipe[0]; const unsigned rem = pipe->remaining; + // BRDY with no armed transfer: a back-to-back data-stage packet beat the PID=NAK below (the + // host has already ACKed it). Park it in the DCP buffer — an unread buffer NAKs further OUTs — + // and let process_pipe0_xfer deliver it when usbd arms the next chunk. BCLR here would silently + // drop the packet and shift every later chunk by one (usbtest ctrl_out corruption at ra4m1). + if (pipe->buf == NULL && rem == 0) { + rusb->DCPCTR = RUSB2_PIPE_CTR_PID_NAK; + return false; + } + const uint16_t mps = edpt0_max_packet_size(rusb); const uint16_t vld = rusb->CFIFOCTR_b.DTLN; const uint16_t len = tu_min16(tu_min16(rem, mps), vld); @@ -360,7 +370,16 @@ static void process_status_completion(uint8_t rhport) dcd_event_xfer_complete(rhport, ep_addr, 0, XFER_RESULT_SUCCESS, true); } -static bool process_pipe0_xfer(rusb2_reg_t *rusb, int buffer_type, uint8_t ep_addr, void *buffer, +// Report a completed transfer on `num` and reset its bookkeeping. Single completion path for the +// BRDY handler and the EP0 parked-packet drain, so they can't diverge (e.g. on clearing `queued`). +static void pipe_xfer_complete(uint8_t rhport, unsigned num, bool in_isr) { + pipe_state_t *pipe = &_dcd.pipe[num]; + pipe->queued = false; + dcd_event_xfer_complete(rhport, pipe->ep, pipe->length - pipe->remaining, + XFER_RESULT_SUCCESS, in_isr); +} + +static bool process_pipe0_xfer(uint8_t rhport, rusb2_reg_t *rusb, int buffer_type, uint8_t ep_addr, void *buffer, uint16_t total_bytes) { uint16_t fifo_sel = (rusb2_is_highspeed_reg(rusb) ? RUSB2_FIFOSEL_MBW_32BIT : RUSB2_FIFOSEL_MBW_16BIT) | FIFOSEL_BIGEND; @@ -386,6 +405,15 @@ static bool process_pipe0_xfer(rusb2_reg_t *rusb, int buffer_type, uint8_t ep_ad /* IN */ TU_ASSERT(rusb->DCPCTR_b.BSTS && (rusb->USBREQ & 0x80)); pipe0_xfer_in(rusb); + } else if (rusb->CFIFOCTR_b.DTLN > 0) { + /* OUT: a back-to-back packet parked by pipe0_xfer_out already sits in the DCP buffer (its + BRDY has fired and been cleared) — deliver it into this chunk now; no new BRDY will come + for it. Runs with the USB IRQ masked (dcd_edpt_xfer). Detected via the hardware DTLN + rather than a driver flag: the BCLR at SETUP/bus-reset then self-heals any parked state. */ + if (pipe0_xfer_out(rusb)) { + pipe_xfer_complete(rhport, 0, false); + return true; // PID stays NAK (set by pipe0_xfer_out) until the next chunk is armed + } } rusb->DCPCTR = RUSB2_PIPE_CTR_PID_BUF; } else { @@ -460,11 +488,11 @@ static bool process_pipe_xfer(rusb2_reg_t* rusb, int buffer_type, uint8_t ep_add return true; } -static bool process_edpt_xfer(rusb2_reg_t* rusb, int buffer_type, uint8_t ep_addr, void* buffer, uint16_t total_bytes) +static bool process_edpt_xfer(uint8_t rhport, rusb2_reg_t* rusb, int buffer_type, uint8_t ep_addr, void* buffer, uint16_t total_bytes) { const unsigned epn = tu_edpt_number(ep_addr); if (0 == epn) { - return process_pipe0_xfer(rusb, buffer_type, ep_addr, buffer, total_bytes); + return process_pipe0_xfer(rhport, rusb, buffer_type, ep_addr, buffer, total_bytes); } else { return process_pipe_xfer(rusb, buffer_type, ep_addr, buffer, total_bytes); } @@ -508,10 +536,7 @@ static void process_pipe_brdy(uint8_t rhport, unsigned num) } } if (completed) { - pipe->queued = false; - dcd_event_xfer_complete(rhport, pipe->ep, - pipe->length - pipe->remaining, - XFER_RESULT_SUCCESS, true); + pipe_xfer_complete(rhport, num, true); // TU_LOG1("C %d %d\r\n", num, pipe->length - pipe->remaining); } } @@ -636,19 +661,8 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { #ifdef RUSB2_SUPPORT_HIGHSPEED if ( rusb2_is_highspeed_rhport(rhport) ) { - rusb->SYSCFG_b.HSE = 1; - - // leave CLKSEL as default (0x11) 24Mhz - - // Power and reset UTMI Phy - uint16_t physet = (rusb->PHYSET | RUSB2_PHYSET_PLLRESET_Msk) & ~RUSB2_PHYSET_DIRPD_Msk; - rusb->PHYSET = physet; - R_BSP_SoftwareDelay((uint32_t) 1, BSP_DELAY_UNITS_MILLISECONDS); - rusb->PHYSET_b.PLLRESET = 0; - - // set UTMI to operating mode and wait for PLL lock confirmation - rusb->LPSTS_b.SUSPENDM = 1; - while (!rusb->PLLSTA_b.PLLLOCK) {} + rusb->SYSCFG_b.HSE = TUD_OPT_HIGH_SPEED ? 1 : 0; // FS-only build: no HS chirp + rusb2_utmi_phy_powerup(rusb); rusb->SYSCFG_b.DRPD = 0; rusb->SYSCFG_b.USBE = 1; @@ -753,6 +767,10 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * ep_desc) if ( !rusb2_is_highspeed_rhport(rhport) && mps > 256) { return false; } + } else if (xfer == TUSB_XFER_INTERRUPT) { + // Interrupt pipes (6-9) have a fixed 64-byte buffer even in high speed (RA6M5 UM 29.1); + // a larger PIPEMAXP would enumerate, then silently truncate every transfer + TU_ASSERT(mps <= 64); } // Re-opening an endpoint must reuse its pipe: usbd_edpt_close() is a no-op on ISO_ALLOC ports, @@ -770,13 +788,12 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * ep_desc) /* setup pipe */ dcd_int_disable(rhport); + rusb->PIPESEL = num; if ( rusb2_is_highspeed_rhport(rhport) ) { - // FIXME shouldn't be after pipe selection and config, also the BUFNMB should be changed - // depending on the allocation scheme + // PIPEBUF is PIPESEL-windowed (RA6M5 UM 29.2.35): write it after selecting the pipe. + // FIXME BUFNMB is a fixed 0x08 for every pipe; a real per-pipe allocation scheme is needed. rusb->PIPEBUF = 0x7C08; } - - rusb->PIPESEL = num; rusb->PIPEMAXP = mps; volatile uint16_t *ctr = get_pipectr(rusb, num); *ctr = RUSB2_PIPE_CTR_ACLRM_Msk | RUSB2_PIPE_CTR_SQCLR_Msk; @@ -860,14 +877,12 @@ bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet _dcd.ep[dir][epn] = num; dcd_int_disable(rhport); + rusb->PIPESEL = (uint16_t) num; if (rusb2_is_highspeed_rhport(rhport)) { - // FIXME (as in dcd_edpt_open): PIPEBUF is a PIPESEL-windowed register (RA6M5 UM §29.2.35) so it - // must be written AFTER PIPESEL selects this pipe, and the fixed BUFNMB=0x08 overlaps every - // HS pipe — a real per-pipe buffer allocator is needed. Left as-is: no RA6M5 HS board on - // the HIL rig to validate a change, and the current mis-ordered write is inert on FS/RA4M1. + // PIPEBUF is PIPESEL-windowed (RA6M5 UM 29.2.35): write it after selecting the pipe. + // FIXME (as in dcd_edpt_open): BUFNMB is a fixed 0x08 for every pipe; a real allocator is needed. rusb->PIPEBUF = 0x7C08; } - rusb->PIPESEL = (uint16_t) num; rusb->PIPEMAXP = largest_packet_size; volatile uint16_t *ctr = get_pipectr(rusb, num); *ctr = RUSB2_PIPE_CTR_ACLRM_Msk | RUSB2_PIPE_CTR_SQCLR_Msk; @@ -893,6 +908,13 @@ bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) volatile uint16_t *ctr = get_pipectr(rusb, num); *ctr = RUSB2_PIPE_CTR_ACLRM_Msk | RUSB2_PIPE_CTR_SQCLR_Msk; // abort in-flight + reset data toggle *ctr = 0; + // a transfer armed before SET_INTERFACE survives to here (no dcd close on this port): drop the + // stale bookkeeping so a BRDY firing before the class re-arms can't replay it + pipe_state_t *pipe = &_dcd.pipe[num]; + pipe->buf = NULL; + pipe->remaining = 0; + pipe->queued = false; + pipe->zlp_pending = false; *ctr = RUSB2_PIPE_CTR_PID_BUF; // enable dcd_int_enable(rhport); return true; @@ -904,7 +926,7 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t t rusb2_reg_t* rusb = RUSB2_REG(rhport); dcd_int_disable(rhport); - bool r = process_edpt_xfer(rusb, 0, ep_addr, buffer, total_bytes); + bool r = process_edpt_xfer(rhport, rusb, 0, ep_addr, buffer, total_bytes); dcd_int_enable(rhport); return r; @@ -917,7 +939,7 @@ bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_ rusb2_reg_t* rusb = RUSB2_REG(rhport); dcd_int_disable(rhport); - bool r = process_edpt_xfer(rusb, 1, ep_addr, ff, total_bytes); + bool r = process_edpt_xfer(rhport, rusb, 1, ep_addr, ff, total_bytes); dcd_int_enable(rhport); return r; @@ -952,6 +974,12 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) } else { const unsigned num = _dcd.ep[0][tu_edpt_number(ep_addr)]; rusb->PIPESEL = (uint16_t)num; + // Drop any packet parked in the buffer while halted: a data-OUT packet the host sent before + // aborting its transfer would otherwise be delivered into the next read after recovery + // (BOT reset + clear-halt re-arms a 31-byte CBW read which then receives stale WRITE data, + // "SCSI CBW is not valid" -> stall -> reset loop; ra6m5 msc write wedge). + *ctr = RUSB2_PIPE_CTR_ACLRM_Msk; + *ctr = 0; // Non-bulk OUT re-enables straight away. Bulk OUT is normally armed together with its transaction // counter (TRE) by process_pipe_xfer(), so we don't blindly re-enable it here — but if a receive // was already armed (still queued), SQCLR above just left it NAKing. Re-assert BUF so it keeps diff --git a/src/portable/renesas/rusb2/hcd_rusb2.c b/src/portable/renesas/rusb2/hcd_rusb2.c index 849551d27..489162e54 100644 --- a/src/portable/renesas/rusb2/hcd_rusb2.c +++ b/src/portable/renesas/rusb2/hcd_rusb2.c @@ -454,11 +454,9 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { if (rusb2_is_highspeed_rhport(rhport) ) { rusb->SYSCFG_b.HSE = 1; rusb->PHYSET_b.HSEB = 0; - rusb->PHYSET_b.DIRPD = 0; - R_BSP_SoftwareDelay((uint32_t) 1, BSP_DELAY_UNITS_MILLISECONDS); - rusb->PHYSET_b.PLLRESET = 0; - rusb->LPSTS_b.SUSPENDM = 1; - while ( !rusb->PLLSTA_b.PLLLOCK ); + // same PHY reference-clock + power-up requirements as dcd_init: without CLKSEL matching the + // board XTAL the PLL never locks and the wait below would spin forever (e.g. EK-RA8M1, 20 MHz) + rusb2_utmi_phy_powerup(rusb); rusb->SYSCFG_b.DRPD = 1; rusb->SYSCFG_b.DCFM = 1; rusb->SYSCFG_b.DPRPU = 0; diff --git a/src/portable/renesas/rusb2/rusb2_ra.h b/src/portable/renesas/rusb2/rusb2_ra.h index e5945ffe2..0954d0d25 100644 --- a/src/portable/renesas/rusb2/rusb2_ra.h +++ b/src/portable/renesas/rusb2/rusb2_ra.h @@ -49,6 +49,23 @@ typedef struct { #define rusb2_is_highspeed_rhport(_p) (_p == 1) #define rusb2_is_highspeed_reg(_reg) (_reg == RUSB2_REG(1)) + + // UTMI PHY reference clock is the main oscillator: PHYSET.CLKSEL must match the board XTAL + // before the PHY PLL is released (RA6M5 UM R01UH0891 29.2.17: 00=12 MHz, 10=20 MHz, + // 11=24 MHz reset default). EK-RA6M5 runs 24 MHz (default works); EK-RA8M1 runs 20 MHz and + // never locks/chirps on the default. A board with a non-standard USB clocking scheme can + // pre-define RUSB2_PHYSET_CLKSEL_VALUE to override this selection. + #ifndef RUSB2_PHYSET_CLKSEL_VALUE + #if BSP_CFG_XTAL_HZ == 12000000 + #define RUSB2_PHYSET_CLKSEL_VALUE 0u + #elif BSP_CFG_XTAL_HZ == 20000000 + #define RUSB2_PHYSET_CLKSEL_VALUE 2u + #elif BSP_CFG_XTAL_HZ == 24000000 + #define RUSB2_PHYSET_CLKSEL_VALUE 3u + #else + #error "USBHS UTMI PHY: no PHYSET.CLKSEL encoding for this BSP_CFG_XTAL_HZ; define RUSB2_PHYSET_CLKSEL_VALUE" + #endif + #endif #else #define RUSB2_CONTROLLER_COUNT 1 @@ -84,6 +101,31 @@ TU_ATTR_ALWAYS_INLINE static inline void rusb2_int_disable(uint8_t rhport) { TU_ATTR_ALWAYS_INLINE static inline void rusb2_phy_init(void) { } +#ifdef RUSB2_SUPPORT_HIGHSPEED +// UTMI PHY power-up per the FSP reference sequence (r_usb_preg_access.c), shared by dcd_init and +// hcd_init: program CLKSEL to the board XTAL while the PHY is powered down (DIRPD=1), 1 us, +// release DIRPD, 1 ms, release PLLRESET, then wait for PLL lock. Changing CLKSEL as the PHY +// powers up gets mis-sampled (EK-RA8M1, 20 MHz). +static inline void rusb2_utmi_phy_powerup(rusb2_reg_t* rusb) { + uint16_t physet = rusb->PHYSET | RUSB2_PHYSET_DIRPD_Msk; + rusb->PHYSET = physet; + #ifdef RUSB2_PHYSET_CLKSEL_VALUE + physet = (uint16_t) ((physet & ~RUSB2_PHYSET_CLKSEL_Msk) | + (RUSB2_PHYSET_CLKSEL_VALUE << RUSB2_PHYSET_CLKSEL_Pos)); + rusb->PHYSET = physet; + #endif + R_BSP_SoftwareDelay((uint32_t) 1, BSP_DELAY_UNITS_MICROSECONDS); + physet &= (uint16_t) ~RUSB2_PHYSET_DIRPD_Msk; + rusb->PHYSET = physet; + R_BSP_SoftwareDelay((uint32_t) 1, BSP_DELAY_UNITS_MILLISECONDS); + rusb->PHYSET_b.PLLRESET = 0; + + // set UTMI to operating mode and wait for PLL lock confirmation + rusb->LPSTS_b.SUSPENDM = 1; + while (!rusb->PLLSTA_b.PLLLOCK) {} +} +#endif + #ifdef __cplusplus } #endif diff --git a/src/tusb_option.h b/src/tusb_option.h index e19ee1629..65cf747e2 100644 --- a/src/tusb_option.h +++ b/src/tusb_option.h @@ -376,7 +376,7 @@ //------------ RUSB2 --------------// #if defined(TUP_USBIP_RUSB2) #define CFG_TUD_EDPT_DEDICATED_HWFIFO 1 - #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE (2 | (TUD_OPT_HIGH_SPEED ? 4 : 0)) // 16 bit and 32 bit if highspeed + #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE (2 | 4) // HS module uses 32-bit access at any link speed (e.g. FS-forced build) #define CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE 0 #define CFG_TUSB_FIFO_HWFIFO_CUSTOM_WRITE // custom write since rusb2 can change access width 32 -> 16 and can write // odd byte with byte access -- cgit v1.3.1 From ca402a0e781eb4d5580030f713377552b72eddda Mon Sep 17 00:00:00 2001 From: hathach Date: Tue, 14 Jul 2026 15:23:07 +0700 Subject: dcd(ci_hs): stale overlay fix; run usbtest on lpcxpresso43s67 - dcd_edpt_stall flushes the primed buffer (ENDPTFLUSH), but the aborted transfer's dQH overlay can be left ACTIVE with mid-transfer state; the next prime after clear-halt then resumes the stale overlay instead of loading the fresh qtd, so post-halt IN reads return mid-buffer data (usbtest case 13 'buf[32] = 56 (not 0)', with case 18 failing downstream of the same corruption in the full battery). qhd_start_xfer now clears overlay.active alongside overlay.halted before linking the new qtd. - test/hil(hfp): drop lpcxpresso43s67's device/usbtest skip - the historical first-case wedge no longer reproduces on this branch, and with the overlay fix the board runs 30/30 on its Fresco xHCI host (previously 28/30 with deterministic case 13/18 failures). mimxrt1064_evk (imxrt dcache path) 30/30 regression-clean. - docs(hil skill): document the external hifiphile rig - pool test/hil/hfp.json, SSH-reachable from htpc/ci with no outbound SSH, exercised by the CI hil-tinyusb (hfp.json) job; never run HIL against it during development unless the user explicitly asks. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_017TQZrFfU3K4Y198aLsUpBC --- .claude/skills/hil/SKILL.md | 6 ++++++ src/portable/chipidea/ci_hs/dcd_ci_hs.c | 4 +++- test/hil/hfp.json | 4 +--- 3 files changed, 10 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/.claude/skills/hil/SKILL.md b/.claude/skills/hil/SKILL.md index 5fcc07bc0..0d3abf1ac 100644 --- a/.claude/skills/hil/SKILL.md +++ b/.claude/skills/hil/SKILL.md @@ -11,9 +11,15 @@ Run TinyUSB HIL tests on real boards. **Run `hostname` first** — it tells you |------|--------------|------------------------| | `htpc` (dev PC) | `test/hil/local.json` | yes (large pool, `test/hil/tinyusb.json`) | | `ci` (the rig) | `test/hil/tinyusb.json` (large pool) | no — can't SSH to htpc, and boards are already local | +| `hifiphile` (external rig) | `test/hil/hfp.json` | no outbound SSH to htpc/ci; SSH-reachable FROM both | Default to **local**. Use **remote** only when on `htpc` and the user says `remote`/`ci.lan`. Never attempt remote on `ci`. +The `hifiphile` rig is externally hosted by TinyUSB maintainer hifiphile; its board pool is +`test/hil/hfp.json` and its HIL runs are triggered by GitHub CI (the `hil-tinyusb (hfp.json)` +matrix job). **Never run HIL against this rig during development unless the user explicitly +asks for it.** + ## Board locks — the CI runner keeps running The `ci` rig also hosts a GitHub Actions runner that flashes boards and runs HIL as part of CI. Hardware access is arbitrated **per board** with kernel flocks in `/tmp/tinyusb-hil-locks/` — do NOT stop the runner service. diff --git a/src/portable/chipidea/ci_hs/dcd_ci_hs.c b/src/portable/chipidea/ci_hs/dcd_ci_hs.c index 55906e678..fa98d6882 100644 --- a/src/portable/chipidea/ci_hs/dcd_ci_hs.c +++ b/src/portable/chipidea/ci_hs/dcd_ci_hs.c @@ -393,7 +393,8 @@ void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { ci_hs_regs_t *dcd_reg = CI_HS_REG(rhport); dcd_reg->ENDPTCTRL[epnum] |= ENDPTCTRL_STALL << (dir ? 16 : 0); - // flush to abort any primed buffer + // flush to abort any primed buffer; the aborted transfer's dQH overlay can be left + // ACTIVE with mid-transfer state - qhd_start_xfer clears it before the next prime dcd_reg->ENDPTFLUSH = TU_BIT(epnum + (dir ? 16 : 0)); } @@ -497,6 +498,7 @@ static void qhd_start_xfer(uint8_t rhport, uint8_t epnum, uint8_t dir) { dcd_qtd_t *p_qtd = &_dcd_data.qtd[epnum][dir]; p_qhd->qtd_overlay.halted = false; // clear any previous error + p_qhd->qtd_overlay.active = false; // a flushed prime leaves stale ACTIVE state; clear it so the fresh qtd loads p_qhd->qtd_overlay.next = (uint32_t)p_qtd; // link qtd to qhd // flush cache diff --git a/test/hil/hfp.json b/test/hil/hfp.json index 3cdc65a34..735d5a402 100644 --- a/test/hil/hfp.json +++ b/test/hil/hfp.json @@ -31,9 +31,7 @@ "name": "lpcxpresso43s67", "uid": "08F000044528BAAA8D858F58C50700F5", "tests": { - "device": true, "host": false, "dual": false, - "skip": ["device/usbtest"], - "comment": "usbtest skipped: ip3511 HS wedges from the first control case (1/30); needs on-rig debugging" + "device": true, "host": false, "dual": false }, "flasher": { "name": "jlink", -- cgit v1.3.1 From 59f02a1c4c18d7e43a1bd6aaad4b50e71931c9ff Mon Sep 17 00:00:00 2001 From: hathach Date: Wed, 15 Jul 2026 02:31:14 +0700 Subject: dwc2: fix EP0 OUT dcache invalidate range; run usbtest on espressif s3/p4 and mimxrt1015 edpt_schedule_packets() advanced xfer->buffer past each armed EP0 chunk, so the OUT-complete handler invalidated the cache at the ADVANCED pointer: one line past the received data. The CPU then read stale cached bytes instead of the DMA'd packet, and the misplaced invalidate discarded a dirty line of whatever variable follows the buffer - random neighbor corruption on every control-OUT data stage. Found by usbtest ctrl_out (cases 14/21) on espressif_p4_function_ev with DMA enabled, the first DWC2 target combining buffer DMA with a data cache: usbd control state wedged after the first control write (every later request stalled), and one build layout panicked in the usbd memcpy with a wild pointer. Rework the EP0 chunk bookkeeping so xfer->buffer always points at the un-consumed position: the arm no longer advances it; instead the EP0 re-arm paths advance past each completed (full) chunk, invalidating it first on the OUT side. The final OUT completion invalidates exactly the received bytes of its last chunk, taken from DOEPDMA ("incremented on every AHB transaction", databook 7.1.83 - the same semantics the SETUP path relies on) before dma_setup_prepare() re-targets it. EP0 chunking state (ep0_pending) is now also dropped on bus reset and on a new SETUP, so a stale latched completion can no longer re-arm EP0 DMA from dead state. No behavior change for targets without dcache. While root-causing, the FIFO layout was cross-checked against the DWC2 databook/programming guide v4.20a: the existing GDFIFOCFG programming (EPInfoBaseAddr = otg_dfifo_depth - 2*ep_count, one SPRAM word per endpoint direction for buffer DMA) is conformant and needs no change; the P4 HS instance's reset GDFIFOCFG (0x03800400) merely reflects a scatter/gather-sized EP_LOC_CNT of 128 that buffer DMA does not need. With the fix in place, enable the usbtest battery on the espressif fleet: tools/build.py allowlists device/usbtest (a plain IDF component like board_test/video_capture) and both espressif boards' only-lists gain device/usbtest. Also re-enable device/usbtest on mimxrt1015_evk: its skip predated the dcd_ci_hs stale-ACTIVE-overlay fix (already on this branch), which cured the battery that previously killed the uPD720201 host controller twice (2026-07-11 ROM fw, 2026-07-13 case 27 on fw 2.0.2.6); rig-validated 30/30 three consecutive runs. Validated on rig (all 30/30): espressif_p4_function_ev(-DMA) (was 22/30 under DMA), espressif_s3_devkitm(-DMA), stm32f723disco(-DMA), mimxrt1015_evk; p4/s3 slave-mode unaffected (DMA-only code path); compile-checked stm32h743nucleo +TUD DMA, stm32f407disco, stm32l476disco (device ports currently on the dead hub). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_017TQZrFfU3K4Y198aLsUpBC --- src/portable/synopsys/dwc2/dcd_dwc2.c | 34 ++++++++++++++++++++++++++-------- test/hil/tinyusb.json | 6 +++--- tools/build.py | 1 + 3 files changed, 30 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c index 6c88b4f27..86aa54510 100644 --- a/src/portable/synopsys/dwc2/dcd_dwc2.c +++ b/src/portable/synopsys/dwc2/dcd_dwc2.c @@ -393,10 +393,6 @@ static void edpt_schedule_packets(uint8_t rhport, const uint8_t epnum, const uin } dep->diepdma = (uintptr_t) xfer->buffer; dep->diepctl = depctl.value; // enable endpoint - // Advance buffer pointer for EP0 - if (epnum == 0) { - xfer->buffer += total_bytes; - } } else #endif { @@ -732,6 +728,8 @@ static void handle_bus_reset(uint8_t rhport) { tu_memclr(xfer_status, sizeof(xfer_status)); + _dcd_data.ep0_pending[TUSB_DIR_OUT] = 0; + _dcd_data.ep0_pending[TUSB_DIR_IN] = 0; _dcd_data.sof_en = false; _dcd_data.allocated_epin_count = 0; @@ -1009,6 +1007,10 @@ static void handle_epout_dma(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepi if (edpt_is_enabled(epin0)) { edpt_disable(rhport, 0x80, false); } + // a new SETUP aborts any in-progress control transfer: drop leftover EP0 chunking state so a + // stale latched completion cannot re-arm from it + _dcd_data.ep0_pending[TUSB_DIR_OUT] = 0; + _dcd_data.ep0_pending[TUSB_DIR_IN] = 0; dcd_dcache_invalidate(_dcd_usbbuf.setup_buffer, sizeof(_dcd_usbbuf.setup_buffer)); @@ -1029,24 +1031,37 @@ static void handle_epout_dma(uint8_t rhport, uint8_t epnum, dwc2_doepint_t doepi // only handle data skip if it is setup or status related // Normal OUT transfer complete if (!doepint_bm.status_phase_rx && !doepint_bm.setup_packet_rx) { + xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT); if ((epnum == 0) && _dcd_data.ep0_pending[TUSB_DIR_OUT]) { - // EP0 can only handle one packet Schedule another packet to be received. + // EP0 can only handle one packet: invalidate and advance past the received bytes, then + // schedule the next. + if (xfer->buffer != NULL) { + dcd_dcache_invalidate(xfer->buffer, CFG_TUD_ENDPOINT0_SIZE); + xfer->buffer += CFG_TUD_ENDPOINT0_SIZE; + } edpt_schedule_packets(rhport, epnum, TUSB_DIR_OUT); } else { dwc2_dep_t* epout = &dwc2->epout[epnum]; - xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT); // determine actual received bytes const dwc2_ep_tsize_t tsiz = {.value = epout->tsiz}; const uint16_t remain = tsiz.xfer_size; xfer->total_len -= remain; + // EP0 invalidates only this (final) chunk's DMA-written bytes: DOEPDMA "is incremented on + // every AHB transaction" (databook 7.1.83), i.e. it points past the last word written. + // Read it before dma_setup_prepare() re-targets it at the setup buffer + uint16_t inval_len = xfer->total_len; + if (epnum == 0) { + inval_len = (uint16_t)(epout->doepdma - (uintptr_t)xfer->buffer); + } + // prepare EP0 for next setup if(epnum == 0) { dma_setup_prepare(rhport); } - dcd_dcache_invalidate(xfer->buffer, xfer->total_len); + dcd_dcache_invalidate(xfer->buffer, inval_len); dcd_event_xfer_complete(rhport, epnum, xfer->total_len, XFER_RESULT_SUCCESS, true); } } @@ -1058,7 +1073,10 @@ static void handle_epin_dma(uint8_t rhport, uint8_t epnum, dwc2_diepint_t diepin if (diepint_bm.xfer_complete) { if ((epnum == 0) && _dcd_data.ep0_pending[TUSB_DIR_IN]) { - // EP0 can only handle one packet. Schedule another packet to be transmitted. + // EP0 can only handle one packet: advance past the sent bytes, then schedule the next. + if (xfer->buffer != NULL) { + xfer->buffer += CFG_TUD_ENDPOINT0_SIZE; + } edpt_schedule_packets(rhport, epnum, TUSB_DIR_IN); } else { dcd_event_xfer_complete(rhport, epnum | TUSB_DIR_IN_MASK, xfer->total_len, XFER_RESULT_SUCCESS, true); diff --git a/test/hil/tinyusb.json b/test/hil/tinyusb.json index 8f121b6f8..8ed33c8a2 100644 --- a/test/hil/tinyusb.json +++ b/test/hil/tinyusb.json @@ -22,11 +22,12 @@ { "name": "espressif_p4_function_ev-DMA", "flags": "-DCFG_TUD_DWC2_DMA_ENABLE=1 -DCFG_TUH_DWC2_DMA_ENABLE=1" } ], "tests": { - "comment": "only IDF/FreeRTOS examples are part of the espressif fleet build; device/usbtest builds under IDF but is not built/flashed by the fleet, so it is not listed", + "comment": "espressif fleet build = IDF/FreeRTOS examples plus the IDF-buildable bare-metal-style ones tools/build.py allowlists (board_test, usbtest, video_capture)", "only": [ "device/cdc_msc_freertos", "device/hid_composite_freertos", "device/audio_test_freertos", + "device/usbtest", "host/device_info", "host/msc_file_explorer_freertos" ], @@ -66,6 +67,7 @@ "device/cdc_msc_freertos", "device/hid_composite_freertos", "device/audio_test_freertos", + "device/usbtest", "host/device_info", "host/msc_file_explorer_freertos" ], @@ -153,8 +155,6 @@ "name": "mimxrt1015_evk", "uid": "DC28F865D2111D228D00B0543A70463C", "tests": { - "skip": ["device/usbtest"], - "comment": "this board's HS battery killed the uPD720201 twice (2026-07-11 on ROM fw, 2026-07-13 case 27 on fw 2.0.2.6 - stop-endpoint timeout, HC died); mimxrt1064/ch32v307 batteries pass, so it is board-specific - keep skipped", "device": true, "host": false, "dual": false diff --git a/tools/build.py b/tools/build.py index 5eaaeb513..51d3d0f70 100755 --- a/tools/build.py +++ b/tools/build.py @@ -92,6 +92,7 @@ def get_examples(family): if family == 'espressif': all_examples.append('device/board_test') + all_examples.append('device/usbtest') all_examples.append('device/video_capture') all_examples.append('host/device_info') all_examples.sort() -- cgit v1.3.1 From a2f0c3c1baea4f0129d14d1a85c7fe8b307a2405 Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 17 Jul 2026 11:40:15 +0700 Subject: rusb2: restore speed-conditional hwfifo stride mask FS-forced builds on the HS module are not a supported configuration, so the 32-bit access path does not need to exist in full-speed builds: CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE returns to (2 | (TUD_OPT_HIGH_SPEED ? 4 : 0)). Saves 28 bytes of text on FS-only parts (measured on ra4m1_ek cdc_msc); high-speed builds keep both widths for the dual-module (FS+HS ports) case. Build-verified: ra4m1_ek and ra6m5_ek cdc_msc. --- src/tusb_option.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/tusb_option.h b/src/tusb_option.h index 65cf747e2..e19ee1629 100644 --- a/src/tusb_option.h +++ b/src/tusb_option.h @@ -376,7 +376,7 @@ //------------ RUSB2 --------------// #if defined(TUP_USBIP_RUSB2) #define CFG_TUD_EDPT_DEDICATED_HWFIFO 1 - #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE (2 | 4) // HS module uses 32-bit access at any link speed (e.g. FS-forced build) + #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE (2 | (TUD_OPT_HIGH_SPEED ? 4 : 0)) // 16 bit and 32 bit if highspeed #define CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE 0 #define CFG_TUSB_FIFO_HWFIFO_CUSTOM_WRITE // custom write since rusb2 can change access width 32 -> 16 and can write // odd byte with byte access -- cgit v1.3.1 From a68d776ac072725266ab53b64b29d4405090281f Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 17 Jul 2026 12:07:50 +0700 Subject: vendor: deactivate de-selected altsetting's isochronous endpoints vendord_set_alt() aborted bulk/interrupt endpoints of the outgoing altsetting (stall/clear-stall) but only dropped the iso endpoints' tracking: an armed iso transfer stayed live in the dcd with its usbd claim held and no tracked handle to stop it, and its completion fired into an endpoint the class no longer recognizes. Reachable through the usbtest example's alt0 (bulk) <-> alt1 (iso) SET_INTERFACE switching. Track each selected iso endpoint's descriptor (points into the app's static descriptor set) and deactivate on de-selection: with the iso-alloc API re-activation is the abort/scrub primitive (resets ep_status, aborts the stale transfer); without it usbd_edpt_close does, and the next selection re-opens. Iso cannot be stalled like bulk/interrupt, hence the separate path. Build-verified: usbtest for stm32f072disco, ra4m1_ek, raspberry_pi_pico (iso-alloc) and ch32v307v_r1_1v0 (close API). --- src/class/vendor/vendor_device.c | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/class/vendor/vendor_device.c b/src/class/vendor/vendor_device.c index 24f1405dc..c4a550ef0 100644 --- a/src/class/vendor/vendor_device.c +++ b/src/class/vendor/vendor_device.c @@ -31,9 +31,11 @@ typedef struct { #if CFG_TUD_VENDOR_EP_ISO_OUT uint8_t ep_iso_out; uint16_t iso_rx_xfer_len; + const tusb_desc_endpoint_t* iso_out_desc; // for deactivation on altsetting de-selection #endif #if CFG_TUD_VENDOR_EP_ISO_IN uint8_t ep_iso_in; + const tusb_desc_endpoint_t* iso_in_desc; // for deactivation on altsetting de-selection #endif #if CFG_TUD_VENDOR_ALT_SETTINGS // implies non-buffered: fields cleared by bus reset uint8_t cur_alt; @@ -483,6 +485,21 @@ static inline bool vendord_iso_ep_alloc(uint8_t rhport, const tusb_desc_endpoint #endif } +// Deactivate a de-selected altsetting's isochronous endpoint: abort any in-flight +// transfer and release its usbd claim so a stale completion cannot fire into a +// no-longer-tracked endpoint (iso cannot be stalled like bulk/interrupt below). With the +// iso-alloc API, (re)activation with the endpoint's descriptor is the abort/scrub +// primitive; without it, close does (the next selection re-opens). +static inline void vendord_iso_ep_deactivate(uint8_t rhport, const tusb_desc_endpoint_t* desc_ep) { + if (desc_ep != NULL) { + #ifdef TUP_DCD_EDPT_ISO_ALLOC + usbd_edpt_iso_activate(rhport, desc_ep); + #else + usbd_edpt_close(rhport, desc_ep->bEndpointAddress); + #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 @@ -526,9 +543,9 @@ static bool vendord_set_alt(uint8_t rhport, uint8_t idx, uint8_t 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.) + // so an 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.) vendord_abort_ep(rhport, p_vendor->ep_in); vendord_abort_ep(rhport, p_vendor->ep_out); #if CFG_TUD_VENDOR_EP_INT_OUT @@ -546,9 +563,13 @@ static bool vendord_set_alt(uint8_t rhport, uint8_t idx, uint8_t alt) { p_vendor->ep_int_in = 0; #endif #if CFG_TUD_VENDOR_EP_ISO_OUT + vendord_iso_ep_deactivate(rhport, p_vendor->iso_out_desc); + p_vendor->iso_out_desc = NULL; p_vendor->ep_iso_out = 0; #endif #if CFG_TUD_VENDOR_EP_ISO_IN + vendord_iso_ep_deactivate(rhport, p_vendor->iso_in_desc); + p_vendor->iso_in_desc = NULL; p_vendor->ep_iso_in = 0; #endif } @@ -604,12 +625,14 @@ static bool vendord_set_alt(uint8_t rhport, uint8_t idx, uint8_t alt) { if (is_in) { TU_ASSERT(vendord_iso_ep_activate(rhport, desc_ep)); p_vendor->ep_iso_in = ep_addr; + p_vendor->iso_in_desc = desc_ep; // points into p_itf_desc (static app descriptor) } #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_out_desc = desc_ep; // points into p_itf_desc (static app descriptor) p_vendor->iso_rx_xfer_len = tu_edpt_packet_size(desc_ep); } #endif -- cgit v1.3.1 From 36cd9f9f46ca20be907ed57b874d9d1dc7b3bf64 Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 16 Jul 2026 14:11:01 +0700 Subject: dcd_lpc17_40: fix stale EP0 out_received, add isochronous support EP0 control-OUT fix (usbtest 14/21, errno 110/-74): usbd queues the status-stage OUT ZLP of every control read with buffer=NULL, so the ISR's `if (out_buffer)` check missed it and marked the arriving ZLP as out_received instead. The stale flag poisoned the next control-OUT with data: its first chunk "completed" instantly from an empty EP0 buffer and the host's real DATA NAKed forever. Track queued transfers with an explicit out_queued flag and void half-finished control state on a new SETUP. Isochronous support (UM10562 12.15.6): 5-word DMA descriptors with per-packet size memory, buflen/present_count in packets, one packet per FRAME (no DMARSet/EpIntEn involvement), completion at EOT for both directions. Details that matter: - the iso machinery (5th DD word + packet-size memory) is compiled only when an iso-capable class is enabled (CFG_TUD_AUDIO/VIDEO/VENDOR), so non-iso builds pay nothing: _dcd stays 648 B vs 1032 B with iso - ISR dispatch keys on the hardware's fixed ep-number/type map (ep_id_is_iso), never on dd fields that thread mode rebuilds - iso OUT honors Packet_valid (bit 16) and prefills the hardware writeback slots with 0, so a missed frame counts as 0 bytes instead of reading back stale buffer contents as data - packet count is validated (tu_div_ceil <= ISO_MAX_PACKETS) before the DD is touched, so an oversized transfer is refused without leaving a serviceable half-built descriptor armed for the frame engine - dcd_edpt_iso_alloc and iso_activate both enforce the fixed iso endpoint numbers (3/6/9/12); classes ignore alloc's return value, so activate must not trust it Un-skip LPC40XX in the usbtest example; tier 4 now enumerates and passes iso cases 15/16/22/23. cdc_msc_throughput and printer_to_cdc had bulk on iso-only EP3 (SET_CONFIGURATION failed with -32); add the LPC17/40 EPNUM block (bulk on EP2/EP5) like other fixed-EP examples. Verified on ea4088_quickstart: usbtest tier-4 battery 30/30 repeatedly and the full device HIL suite 14/14 (incl. audio_test iso). --- .../cdc_msc_throughput/src/usb_descriptors.c | 10 +- .../device/printer_to_cdc/src/usb_descriptors.c | 10 +- examples/device/usbtest/skip.txt | 1 - src/portable/nxp/lpc17_40/dcd_lpc17_40.c | 211 ++++++++++++++++++--- 4 files changed, 202 insertions(+), 30 deletions(-) (limited to 'src') diff --git a/examples/device/cdc_msc_throughput/src/usb_descriptors.c b/examples/device/cdc_msc_throughput/src/usb_descriptors.c index ba0b0a26f..dca5a65cf 100644 --- a/examples/device/cdc_msc_throughput/src/usb_descriptors.c +++ b/examples/device/cdc_msc_throughput/src/usb_descriptors.c @@ -65,7 +65,15 @@ enum { }; // Place bulk endpoints on EP>=8 for MAX32690 class parts (bigger FIFO, DPB-capable). -#if CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY +#if CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC177X_8X || CFG_TUSB_MCU == OPT_MCU_LPC40XX + // LPC 17xx and 40xx endpoint type (bulk/interrupt/iso) are fixed by its number + // 0 control, 1 In, 2 Bulk, 3 Iso, 4 In, 5 Bulk etc ... + #define EPNUM_CDC_NOTIF 0x81 + #define EPNUM_CDC_OUT 0x02 + #define EPNUM_CDC_IN 0x82 + #define EPNUM_MSC_OUT 0x05 + #define EPNUM_MSC_IN 0x85 +#elif CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY #if TU_CHECK_MCU(OPT_MCU_MAX32650, OPT_MCU_MAX32666, OPT_MCU_MAX32690, OPT_MCU_MAX78002) // Put bulk on EP>=8 so the 2048/4096-byte FIFOs can back double packet buffering #define EPNUM_CDC_NOTIF 0x81 diff --git a/examples/device/printer_to_cdc/src/usb_descriptors.c b/examples/device/printer_to_cdc/src/usb_descriptors.c index b9450c87e..92cd2b6be 100644 --- a/examples/device/printer_to_cdc/src/usb_descriptors.c +++ b/examples/device/printer_to_cdc/src/usb_descriptors.c @@ -67,7 +67,15 @@ uint8_t const *tud_descriptor_device_cb(void) { //--------------------------------------------------------------------+ // Endpoint numbers -#if CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY +#if CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC177X_8X || CFG_TUSB_MCU == OPT_MCU_LPC40XX + // LPC 17xx and 40xx endpoint type (bulk/interrupt/iso) are fixed by its number + // 0 control, 1 In, 2 Bulk, 3 Iso, 4 In, 5 Bulk etc ... + #define EPNUM_CDC_NOTIF 0x81 + #define EPNUM_CDC_OUT 0x02 + #define EPNUM_CDC_IN 0x82 + #define EPNUM_PRINTER_OUT 0x05 + #define EPNUM_PRINTER_IN 0x85 +#elif CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY #if TU_CHECK_MCU(OPT_MCU_MAX32650, OPT_MCU_MAX32666, OPT_MCU_MAX32690, OPT_MCU_MAX78002) // Put bulk on EP>=8 so the 2048/4096-byte FIFOs can back double packet buffering #define EPNUM_CDC_NOTIF 0x81 diff --git a/examples/device/usbtest/skip.txt b/examples/device/usbtest/skip.txt index b52bdbb14..e789c4b91 100644 --- a/examples/device/usbtest/skip.txt +++ b/examples/device/usbtest/skip.txt @@ -5,7 +5,6 @@ mcu:SAMD11 mcu:CXD56 mcu:FT90X mcu:LPC175X_6X -mcu:LPC40XX mcu:NUC100 mcu:NUC120 mcu:NUC505 diff --git a/src/portable/nxp/lpc17_40/dcd_lpc17_40.c b/src/portable/nxp/lpc17_40/dcd_lpc17_40.c index 182710016..a1a44e9ae 100644 --- a/src/portable/nxp/lpc17_40/dcd_lpc17_40.c +++ b/src/portable/nxp/lpc17_40/dcd_lpc17_40.c @@ -19,6 +19,10 @@ //--------------------------------------------------------------------+ #define DCD_ENDPOINT_MAX 32 +// The iso machinery (5th DD word + packet-size memory) costs USB RAM on every build; +// compile it only when a class that can open an iso endpoint is enabled. +#define DCD_ISO_ENABLED (CFG_TUD_AUDIO || CFG_TUD_VIDEO || CFG_TUD_VENDOR) + typedef struct TU_ATTR_ALIGNED(4) { //------------- Word 0 -------------// @@ -48,11 +52,35 @@ typedef struct TU_ATTR_ALIGNED(4) volatile uint16_t present_count; // For non-iso : The number of bytes transferred by the DMA engine // For iso : number of packets +#if DCD_ISO_ENABLED //------------- Word 4 -------------// - // uint32_t iso_packet_size_addr; // iso only, can be omitted for non-iso + volatile uint32_t iso_packet_size_addr; // iso only: pointer into iso packet-size memory, + // advanced by hardware after each packet +#endif }dma_desc_t; -TU_VERIFY_STATIC( sizeof(dma_desc_t) == 16, "size is not correct"); // TODO not support ISO for now +TU_VERIFY_STATIC( sizeof(dma_desc_t) == (DCD_ISO_ENABLED ? 20 : 16), "size is not correct"); + +// Hardware fixes endpoint type by number: 3, 6, 9, 12 are the iso-capable ones. +// Constant per ep_id (= 2*epnum + dir) — unlike dd->isochronous, which dcd_edpt_xfer +// transiently zeroes while rebuilding the DD, this is safe to dispatch on from the ISR. +TU_ATTR_ALWAYS_INLINE static inline bool ep_id_is_iso(uint8_t ep_id) { + uint8_t const epnum = (uint8_t)(ep_id >> 1); + return (epnum % 3) == 0 && (epnum != 0) && (epnum != 15); +} + +#if DCD_ISO_ENABLED +// Isochronous packet-size memory (UM10562 12.15.6.3): one word per packet. +// IN : software fills Packet_length (bits 15:0), 0 = ZLP +// OUT: hardware writes Frame_number (31:17) | Packet_valid (16) | Packet_length (15:0) +// Iso-capable endpoint numbers are 3, 6, 9, 12 -> 8 slots (x2 directions). +// One packet moves per FRAME, so a deep queue only adds latency: 8 frames is plenty. +#define ISO_MAX_PACKETS 8 +#define ISO_SLOT_COUNT 8 +TU_ATTR_ALWAYS_INLINE static inline uint8_t iso_slot(uint8_t ep_id) { + return (uint8_t)(((ep_id / 6) - 1) * 2 + (ep_id & 1)); // ep_id = 2*epnum + dir, epnum in {3,6,9,12} +} +#endif typedef struct { @@ -66,11 +94,17 @@ typedef struct { uint8_t* out_buffer; uint8_t out_bytes; + volatile bool out_queued; // an OUT xfer is queued; out_buffer may legitimately be NULL (status ZLP) volatile bool out_received; // indicate if data is already received in endpoint uint8_t in_bytes; } control; +#if DCD_ISO_ENABLED + // iso packet-size memory, must be DMA-reachable like the DDs + volatile uint32_t iso_psize[ISO_SLOT_COUNT][ISO_MAX_PACKETS]; +#endif + } dcd_data_t; CFG_TUD_MEM_SECTION TU_ATTR_ALIGNED(128) static dcd_data_t _dcd; @@ -79,6 +113,7 @@ CFG_TUD_MEM_SECTION TU_ATTR_ALIGNED(128) static dcd_data_t _dcd; //--------------------------------------------------------------------+ // SIE Command //--------------------------------------------------------------------+ + static void sie_cmd_code (sie_cmdphase_t phase, uint8_t code_data) { LPC_USB->DevIntClr = (DEV_INT_COMMAND_CODE_EMPTY_MASK | DEV_INT_COMMAND_DATA_FULL_MASK); @@ -294,7 +329,8 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) break; case TUSB_XFER_ISOCHRONOUS: - TU_ASSERT((epnum % 3) == 0 && (epnum != 0) && (epnum != 15)); + // iso machinery is compiled out when no iso-capable class is enabled + TU_ASSERT(DCD_ISO_ENABLED && (epnum % 3) == 0 && (epnum != 0) && (epnum != 15)); break; default: @@ -319,16 +355,54 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) } bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { +#if DCD_ISO_ENABLED (void)rhport; - (void)ep_addr; - (void)largest_packet_size; + uint8_t const ep_id = ep_addr2idx(ep_addr); + + // hardware fixes iso to endpoint numbers 3, 6, 9, 12 + TU_ASSERT(ep_id_is_iso(ep_id)); + TU_ASSERT(largest_packet_size > 0); + + set_ep_size(ep_id, largest_packet_size); + + dma_desc_t* const dd = &_dcd.dd[ep_id]; + tu_memclr(dd, sizeof(dma_desc_t)); + dd->isochronous = 1; + dd->max_packet_size = largest_packet_size; + dd->retired = 1; // invalid at first + + sie_write(SIE_CMDCODE_ENDPOINT_SET_STATUS + ep_id, 1, 0); + return true; +#else + (void)rhport; (void)ep_addr; (void)largest_packet_size; return false; +#endif } bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) { +#if DCD_ISO_ENABLED (void)rhport; - (void)desc_ep; + uint8_t const ep_id = ep_addr2idx(desc_ep->bEndpointAddress); + dma_desc_t* const dd = &_dcd.dd[ep_id]; + + // same fixed-number rule as alloc: without it a rejected-but-ignored alloc (classes + // discard that return) would set isochronous on a non-iso ep_id and underflow iso_slot() + TU_ASSERT(ep_id_is_iso(ep_id)); + + // kill any armed transfer from a previous alternate setting + LPC_USB->EpDMADis = TU_BIT(ep_id); + _dcd.udca[ep_id] = NULL; + + dd->isochronous = 1; + dd->max_packet_size = tu_edpt_packet_size(desc_ep); + dd->retired = 1; + + sie_write(SIE_CMDCODE_ENDPOINT_SET_STATUS + ep_id, 1, 0); + return true; +#else + (void)rhport; (void)desc_ep; return false; +#endif } void dcd_edpt_close_all (uint8_t rhport) @@ -373,15 +447,17 @@ static bool control_xact(uint8_t rhport, uint8_t dir, uint8_t * buffer, uint8_t { // Already received the DATA OUT packet _dcd.control.out_received = false; - _dcd.control.out_buffer = NULL; - _dcd.control.out_bytes = 0; uint8_t received = control_ep_read(buffer, len); dcd_event_xfer_complete(0, 0, received, XFER_RESULT_SUCCESS, true); }else { + // buffer is NULL for a status-stage ZLP: signal the pending xfer explicitly, + // NOT via out_buffer != NULL — a NULL-buffer queue mistaken for "nothing queued" + // leaves out_received stale and poisons the next control OUT data stage. _dcd.control.out_buffer = buffer; _dcd.control.out_bytes = len; + _dcd.control.out_queued = true; } } @@ -406,26 +482,65 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t t uint16_t const ep_size = dd->max_packet_size; uint8_t is_iso = dd->isochronous; - tu_memclr(dd, sizeof(dma_desc_t)); - dd->isochronous = is_iso; - dd->max_packet_size = ep_size; - dd->buffer = (uint32_t) buffer; - dd->buflen = total_bytes; +#if DCD_ISO_ENABLED + if ( is_iso ) + { + // iso: buflen counts packets; per-packet sizes live in the packet-size memory. + // One packet moves per frame (UM10562 12.15.6: DMA request is raised for + // DMA-enabled iso endpoints on every FRAME interrupt, both directions). + // Validate BEFORE touching the DD: bailing out mid-rebuild would leave a + // zeroed (retired=0 -> serviceable) descriptor armed for the frame engine. + TU_ASSERT(ep_size > 0); + uint16_t const packets = (total_bytes > 0) ? (uint16_t) tu_div_ceil(total_bytes, ep_size) : 1; + TU_ASSERT(packets <= ISO_MAX_PACKETS); + + uint8_t const slot = iso_slot(ep_id); + uint16_t remain = total_bytes; + for ( uint16_t i = 0; i < packets; i++ ) + { + uint16_t const pkt_len = tu_min16(remain, ep_size); + // IN: length to send (0 = ZLP). OUT: hardware writes back + // Frame_number|Packet_valid|Packet_length -- prefill 0 so a frame the + // hardware never wrote (missed/invalid) cannot read back as data. + _dcd.iso_psize[slot][i] = (ep_id & 1) ? pkt_len : 0; + remain = (uint16_t)(remain - pkt_len); + } - _dcd.udca[ep_id] = dd; + tu_memclr(dd, sizeof(dma_desc_t)); + dd->isochronous = 1; + dd->max_packet_size = ep_size; + dd->buffer = (uint32_t) buffer; + dd->buflen = packets; + dd->iso_packet_size_addr = (uint32_t) &_dcd.iso_psize[slot][0]; - if ( ep_id % 2 ) + _dcd.udca[ep_id] = dd; + LPC_USB->EpDMAEn = TU_BIT(ep_id); // frame-triggered: no DMARSet, no EpIntEn + } + else +#else + (void) is_iso; +#endif { - // Clear EP interrupt before Enable DMA - LPC_USB->EpIntEn &= ~TU_BIT(ep_id); - LPC_USB->EpDMAEn = TU_BIT(ep_id); + tu_memclr(dd, sizeof(dma_desc_t)); + dd->max_packet_size = ep_size; + dd->buffer = (uint32_t) buffer; + dd->buflen = total_bytes; - // endpoint IN need to actively raise DMA request - LPC_USB->DMARSet = TU_BIT(ep_id); - }else - { - // Enable DMA - LPC_USB->EpDMAEn = TU_BIT(ep_id); + _dcd.udca[ep_id] = dd; + + if ( ep_id % 2 ) + { + // Clear EP interrupt before Enable DMA + LPC_USB->EpIntEn &= ~TU_BIT(ep_id); + LPC_USB->EpDMAEn = TU_BIT(ep_id); + + // endpoint IN need to actively raise DMA request + LPC_USB->DMARSet = TU_BIT(ep_id); + }else + { + // Enable DMA + LPC_USB->EpDMAEn = TU_BIT(ep_id); + } } return true; @@ -451,13 +566,20 @@ static void control_xfer_isr(uint8_t rhport, uint32_t ep_int_status) uint8_t setup_packet[8]; control_ep_read(setup_packet, 8); // TODO read before clear setup above + // a new SETUP voids any half-finished control state + _dcd.control.out_queued = false; + _dcd.control.out_received = false; + _dcd.control.out_buffer = NULL; + _dcd.control.out_bytes = 0; + dcd_event_setup_received(rhport, setup_packet, true); } - else if ( _dcd.control.out_buffer ) + else if ( _dcd.control.out_queued ) { - // software queued transfer previously + // software queued transfer previously (out_buffer NULL = status ZLP) uint8_t received = control_ep_read(_dcd.control.out_buffer, _dcd.control.out_bytes); + _dcd.control.out_queued = false; _dcd.control.out_buffer = NULL; _dcd.control.out_bytes = 0; @@ -513,7 +635,32 @@ static void dd_complete_isr(uint8_t rhport, uint8_t ep_id) uint8_t result = (dd->status == DD_STATUS_NORMAL || dd->status == DD_STATUS_DATA_UNDERUN) ? XFER_RESULT_SUCCESS : XFER_RESULT_FAILED; uint8_t const ep_addr = (ep_id / 2) | ((ep_id & 0x01) ? TUSB_DIR_IN_MASK : 0); - dcd_event_xfer_complete(rhport, ep_addr, dd->present_count, result, true); + uint32_t xferred_bytes; +#if DCD_ISO_ENABLED + if ( ep_id_is_iso(ep_id) ) + { + // present_count is in packets; actual byte counts are in the packet-size memory + // (IN: as programmed by us, OUT: Packet_length written back by hardware, + // guarded by Packet_valid -- a frame with no packet must count as 0) + uint8_t const slot = iso_slot(ep_id); + uint16_t const packets = tu_min16(dd->present_count, ISO_MAX_PACKETS); + xferred_bytes = 0; + for (uint16_t i = 0; i < packets; i++) + { + uint32_t const psize = _dcd.iso_psize[slot][i]; + if ( (ep_id & 1) || (psize & TU_BIT(16)) ) + { + xferred_bytes += (psize & 0xFFFFu); + } + } + } + else +#endif + { + xferred_bytes = dd->present_count; + } + + dcd_event_xfer_complete(rhport, ep_addr, (uint16_t) xferred_bytes, result, true); } // main USB IRQ handler @@ -569,6 +716,16 @@ void dcd_int_handler(uint8_t rhport) { if ( tu_bit_test(eot, ep_id) ) { + // dispatch on the hardware's fixed ep-number/type map, NOT dd->isochronous: + // thread-mode dcd_edpt_xfer transiently zeroes the DD while rebuilding it +#if DCD_ISO_ENABLED + if ( ep_id_is_iso(ep_id) ) + { + // iso: last packet already left with its frame; complete both directions here + dd_complete_isr(rhport, ep_id); + } + else +#endif if ( ep_id & 0x01 ) { // IN enable EpInt for end of usb transfer -- cgit v1.3.1 From a3ee0b4ff12615552de50bd2a61287fdb0b11bd9 Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 16 Jul 2026 14:11:04 +0700 Subject: dcd_lpc17_40: mask USB IRQ around non-reentrant SIE and realization sequences The SIE command protocol (CmdCode + CCEMPTY/CDFULL handshake), the slave-mode Ctrl/RxData/TxData window, the EpIntEn read-modify-writes, and set_ep_size's ReEp/EP_RLZED handshake are all shared between thread-mode API calls and dcd_int_handler, and none are reentrant: an ISR preempting a thread-mode sequence consumes its handshake flags or, in set_ep_size's case, a bus reset's DevIntClr = 0xFFFFFFFF eats the EP_RLZED flag the spin waits on, hanging it forever. Guard them by masking only the USB IRQ (nestable, ISR-safe; CMSIS NVIC_DisableIRQ already ends with DSB+ISB). control_xact keeps the mask across its in_isr=true event push, since osal_none skips queue locking for in_isr. Hardening, not a fix for an observed failure: the ea4088 usbtest 30/30 + HIL 14/14 results were reproduced with and without it. The windows are a few instructions wide and most exposed on RTOS builds where class drivers queue transfers from tasks concurrent with the USB IRQ. --- src/portable/nxp/lpc17_40/dcd_lpc17_40.c | 56 +++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/portable/nxp/lpc17_40/dcd_lpc17_40.c b/src/portable/nxp/lpc17_40/dcd_lpc17_40.c index a1a44e9ae..6dc2b017c 100644 --- a/src/portable/nxp/lpc17_40/dcd_lpc17_40.c +++ b/src/portable/nxp/lpc17_40/dcd_lpc17_40.c @@ -114,6 +114,28 @@ CFG_TUD_MEM_SECTION TU_ATTR_ALIGNED(128) static dcd_data_t _dcd; // SIE Command //--------------------------------------------------------------------+ +// The SIE command protocol (CmdCode + CCEMPTY/CDFULL handshake) and the +// slave-mode Ctrl/RxData/TxData registers are shared between thread-mode API +// calls and dcd_int_handler, and are not reentrant: an ISR preempting a +// thread-mode SIE sequence consumes its handshake flags and overwrites +// CmdCode (symptom: EP0 wedges/answers stale data right after SET_INTERFACE +// stall/clear-stall bursts overlapping bulk EOT interrupts). Mask only the +// USB interrupt around those sequences; safe to nest, including from the ISR. +static inline bool usb_irq_lock(void) +{ + bool const enabled = NVIC_GetEnableIRQ(USB_IRQn) != 0; + if (enabled) + { + NVIC_DisableIRQ(USB_IRQn); // CMSIS already ends this with DSB+ISB + } + return enabled; +} + +static inline void usb_irq_unlock(bool enabled) +{ + if (enabled) NVIC_EnableIRQ(USB_IRQn); +} + static void sie_cmd_code (sie_cmdphase_t phase, uint8_t code_data) { LPC_USB->DevIntClr = (DEV_INT_COMMAND_CODE_EMPTY_MASK | DEV_INT_COMMAND_DATA_FULL_MASK); @@ -127,19 +149,28 @@ static void sie_cmd_code (sie_cmdphase_t phase, uint8_t code_data) static void sie_write (uint8_t cmd_code, uint8_t data_len, uint8_t data) { + bool const lock = usb_irq_lock(); + sie_cmd_code(SIE_CMDPHASE_COMMAND, cmd_code); if (data_len) { sie_cmd_code(SIE_CMDPHASE_WRITE, data); } + + usb_irq_unlock(lock); } static uint8_t sie_read (uint8_t cmd_code) { + bool const lock = usb_irq_lock(); + sie_cmd_code(SIE_CMDPHASE_COMMAND , cmd_code); sie_cmd_code(SIE_CMDPHASE_READ , cmd_code); - return (uint8_t) LPC_USB->CmdData; + uint8_t const data = (uint8_t) LPC_USB->CmdData; + + usb_irq_unlock(lock); + return data; } //--------------------------------------------------------------------+ @@ -152,6 +183,11 @@ static inline uint8_t ep_addr2idx(uint8_t ep_addr) static void set_ep_size(uint8_t ep_id, uint16_t max_packet_size) { + // ReEp RMW + the EP_RLZED handshake share DevIntSt with the ISR: a bus reset + // from dcd_int_handler writes DevIntClr = 0xFFFFFFFF and would consume the + // flag this spin waits on, hanging it forever -> same lock as the SIE paths. + bool const lock = usb_irq_lock(); + // follows example in 11.10.4.2 LPC_USB->ReEp |= TU_BIT(ep_id); LPC_USB->EpInd = ep_id; // select index before setting packet size @@ -159,6 +195,8 @@ static void set_ep_size(uint8_t ep_id, uint16_t max_packet_size) while ((LPC_USB->DevIntSt & DEV_INT_ENDPOINT_REALIZED_MASK) == 0) {} LPC_USB->DevIntClr = DEV_INT_ENDPOINT_REALIZED_MASK; + + usb_irq_unlock(lock); } @@ -265,6 +303,7 @@ static inline uint8_t byte2dword(uint8_t bytes) static void control_ep_write(void const * buffer, uint8_t len) { uint32_t const * buf32 = (uint32_t const *) buffer; + bool const lock = usb_irq_lock(); // Ctrl/TxData + SIE sequence must not interleave with the ISR LPC_USB->Ctrl = USBCTRL_WRITE_ENABLE_MASK; // logical endpoint = 0 LPC_USB->TxPLen = (uint32_t) len; @@ -280,10 +319,14 @@ static void control_ep_write(void const * buffer, uint8_t len) // select control IN & validate the endpoint sie_write(SIE_CMDCODE_ENDPOINT_SELECT+1, 0, 0); sie_write(SIE_CMDCODE_BUFFER_VALIDATE , 0, 0); + + usb_irq_unlock(lock); } static uint8_t control_ep_read(void * buffer, uint8_t len) { + bool const lock = usb_irq_lock(); // Ctrl/RxData + SIE sequence must not interleave with the ISR + LPC_USB->Ctrl = USBCTRL_READ_ENABLE_MASK; // logical endpoint = 0 while ((LPC_USB->RxPLen & USBRXPLEN_PACKET_READY_MASK) == 0) {} // TODO blocking, should have timeout @@ -302,6 +345,7 @@ static uint8_t control_ep_read(void * buffer, uint8_t len) sie_write(SIE_CMDCODE_ENDPOINT_SELECT+0, 0, 0); sie_write(SIE_CMDCODE_BUFFER_CLEAR , 0, 0); + usb_irq_unlock(lock); return len; } @@ -443,13 +487,19 @@ static bool control_xact(uint8_t rhport, uint8_t dir, uint8_t * buffer, uint8_t control_ep_write(buffer, len); }else { + // guard the out_received/out_buffer handshake against the EP0 OUT ISR + bool const lock = usb_irq_lock(); + if ( _dcd.control.out_received ) { // Already received the DATA OUT packet _dcd.control.out_received = false; uint8_t received = control_ep_read(buffer, len); + // event queued with in_isr=true, which skips the queue's own locking: keep the + // USB IRQ masked across it, or a real ISR completion could interleave the write dcd_event_xfer_complete(0, 0, received, XFER_RESULT_SUCCESS, true); + usb_irq_unlock(lock); }else { // buffer is NULL for a status-stage ZLP: signal the pending xfer explicitly, @@ -458,6 +508,7 @@ static bool control_xact(uint8_t rhport, uint8_t dir, uint8_t * buffer, uint8_t _dcd.control.out_buffer = buffer; _dcd.control.out_bytes = len; _dcd.control.out_queued = true; + usb_irq_unlock(lock); } } @@ -531,8 +582,11 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t t if ( ep_id % 2 ) { // Clear EP interrupt before Enable DMA + // EpIntEn read-modify-write races the ISR's own RMWs -> lock + bool const lock = usb_irq_lock(); LPC_USB->EpIntEn &= ~TU_BIT(ep_id); LPC_USB->EpDMAEn = TU_BIT(ep_id); + usb_irq_unlock(lock); // endpoint IN need to actively raise DMA request LPC_USB->DMARSet = TU_BIT(ep_id); -- cgit v1.3.1 From fa1fee0a5f82b5a78ace26ee1722c1d636c5b32a Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 10 Jul 2026 00:17:18 +0700 Subject: migrate NXP Kinetis khci to chipidea ci_fs driver (device + host) Complete the khci -> chipidea ci_fs migration that was started for device (commit d70403f1f "host is not yet"): - device: switch kinetis_k/kl/k32l (Makefiles + k32l CMake) to dcd_ci_fs.c - host: add hcd_ci_fs.c (port of hcd_khci.c onto ci_fs_regs_t) and switch all Kinetis families to it; remove src/portable/nxp/khci entirely - enable host examples (device_info, cdc_msc_hid) for mcu:KINETIS_K - README: merge the KL and K32L2 rows into a single "KL, K32L" ci_fs row hcd_ci_fs.c also fixes two pre-existing host bugs found via HIL on frdm_k64f (present in the old hcd_khci.c too): - data toggle was flipped on a NAK in suspend_transfer; a NAK transfers no data so the toggle must be preserved, else the retried bulk packet is silently discarded by the device (MSC CBW/CSW hang). See comment in file. - prepare_packets asserted and dropped a transfer when the single shared BDT was still owned by an in-flight transfer under concurrent activity; now it returns busy and resume_transfer defers/retries on the next SOF. HIL verified on frdm_k64f: device 13/13, host cdc_msc_hid (CDC mount + echo + MSC mount, through a hub). Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01ExGPLP5eU43LR7o6yYLpNi --- README.rst | 4 +- examples/host/cdc_msc_hid/only.txt | 1 + examples/host/device_info/only.txt | 1 + hw/bsp/kinetis_k/family.cmake | 2 +- hw/bsp/kinetis_k/family.mk | 4 +- hw/bsp/kinetis_k32l/family.cmake | 4 +- hw/bsp/kinetis_k32l/family.mk | 4 +- hw/bsp/kinetis_kl/family.cmake | 2 +- hw/bsp/kinetis_kl/family.mk | 4 +- src/portable/chipidea/ci_fs/hcd_ci_fs.c | 649 ++++++++++++++++++++++++++++++++ src/portable/nxp/khci/dcd_khci.c | 560 --------------------------- src/portable/nxp/khci/hcd_khci.c | 628 ------------------------------ 12 files changed, 662 insertions(+), 1201 deletions(-) create mode 100644 src/portable/chipidea/ci_fs/hcd_ci_fs.c delete mode 100644 src/portable/nxp/khci/dcd_khci.c delete mode 100644 src/portable/nxp/khci/hcd_khci.c (limited to 'src') diff --git a/README.rst b/README.rst index e5806bafc..205f3f544 100644 --- a/README.rst +++ b/README.rst @@ -239,9 +239,7 @@ Supported CPUs +--------------+---------+-------------------+--------+------+-----------+------------------------+--------------------+ | NXP | iMXRT | RT 10xx, 11xx | ✅ | ✅ | ✅ | ci_hs, ehci | | | +---------+-------------------+--------+------+-----------+------------------------+--------------------+ -| | Kinetis | KL | ✅ | 🟡 | ❌ | ci_fs, khci | | -| | +-------------------+--------+------+-----------+------------------------+--------------------+ -| | | K32L2 | ✅ | | ❌ | khci | ci_fs variant | +| | Kinetis | KL, K32L | ✅ | 🟡 | ❌ | ci_fs | | | +---------+-------------------+--------+------+-----------+------------------------+--------------------+ | | LPC | 11u, 13, 15 | ✅ | ❌ | ❌ | lpc_ip3511 | | | | +-------------------+--------+------+-----------+------------------------+--------------------+ diff --git a/examples/host/cdc_msc_hid/only.txt b/examples/host/cdc_msc_hid/only.txt index a2ff93be5..a2f4f273a 100644 --- a/examples/host/cdc_msc_hid/only.txt +++ b/examples/host/cdc_msc_hid/only.txt @@ -3,6 +3,7 @@ family:samd21 family:samd5x_e5x mcu:CH32V20X mcu:KINETIS_KL +mcu:KINETIS_K mcu:LPC175X_6X mcu:LPC177X_8X mcu:LPC18XX diff --git a/examples/host/device_info/only.txt b/examples/host/device_info/only.txt index 4c2cb0f35..7f30218df 100644 --- a/examples/host/device_info/only.txt +++ b/examples/host/device_info/only.txt @@ -4,6 +4,7 @@ family:samd21 family:samd5x_e5x mcu:CH32V20X mcu:KINETIS_KL +mcu:KINETIS_K mcu:LPC175X_6X mcu:LPC177X_8X mcu:LPC18XX diff --git a/hw/bsp/kinetis_k/family.cmake b/hw/bsp/kinetis_k/family.cmake index e1b5c221e..e408c0f4a 100644 --- a/hw/bsp/kinetis_k/family.cmake +++ b/hw/bsp/kinetis_k/family.cmake @@ -63,7 +63,7 @@ function(family_configure_example TARGET RTOS) ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/family.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../board.c ${TOP}/src/portable/chipidea/ci_fs/dcd_ci_fs.c - ${TOP}/src/portable/nxp/khci/hcd_khci.c + ${TOP}/src/portable/chipidea/ci_fs/hcd_ci_fs.c ${STARTUP_FILE_${CMAKE_C_COMPILER_ID}} ) target_include_directories(${TARGET} PUBLIC diff --git a/hw/bsp/kinetis_k/family.mk b/hw/bsp/kinetis_k/family.mk index b1e1fb3aa..5d0e4a702 100644 --- a/hw/bsp/kinetis_k/family.mk +++ b/hw/bsp/kinetis_k/family.mk @@ -17,8 +17,8 @@ LDFLAGS += \ --specs=nosys.specs --specs=nano.specs \ SRC_C += \ - src/portable/nxp/khci/dcd_khci.c \ - src/portable/nxp/khci/hcd_khci.c \ + src/portable/chipidea/ci_fs/dcd_ci_fs.c \ + src/portable/chipidea/ci_fs/hcd_ci_fs.c \ $(MCU_DIR)/system_${MCU_VARIANT}.c \ $(MCU_DIR)/drivers/fsl_clock.c \ $(SDK_DIR)/drivers/gpio/fsl_gpio.c \ diff --git a/hw/bsp/kinetis_k32l/family.cmake b/hw/bsp/kinetis_k32l/family.cmake index 020695589..950682363 100644 --- a/hw/bsp/kinetis_k32l/family.cmake +++ b/hw/bsp/kinetis_k32l/family.cmake @@ -64,8 +64,8 @@ function(family_configure_example TARGET RTOS) target_sources(${TARGET} PUBLIC ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/family.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../board.c - ${TOP}/src/portable/nxp/khci/dcd_khci.c - ${TOP}/src/portable/nxp/khci/hcd_khci.c + ${TOP}/src/portable/chipidea/ci_fs/dcd_ci_fs.c + ${TOP}/src/portable/chipidea/ci_fs/hcd_ci_fs.c ${STARTUP_FILE_${CMAKE_C_COMPILER_ID}} ) target_include_directories(${TARGET} PUBLIC diff --git a/hw/bsp/kinetis_k32l/family.mk b/hw/bsp/kinetis_k32l/family.mk index a99fb5dbe..357128aa5 100644 --- a/hw/bsp/kinetis_k32l/family.mk +++ b/hw/bsp/kinetis_k32l/family.mk @@ -13,8 +13,8 @@ LDFLAGS += \ -specs=nosys.specs -specs=nano.specs SRC_C += \ - src/portable/nxp/khci/dcd_khci.c \ - src/portable/nxp/khci/hcd_khci.c \ + src/portable/chipidea/ci_fs/dcd_ci_fs.c \ + src/portable/chipidea/ci_fs/hcd_ci_fs.c \ $(MCUX_DEVICES)/K32L/$(MCU_VARIANT)/system_$(MCU_VARIANT).c \ $(MCUX_DEVICES)/K32L/$(MCU_VARIANT)/drivers/fsl_clock.c \ $(MCUX_CORE)/drivers/gpio/fsl_gpio.c \ diff --git a/hw/bsp/kinetis_kl/family.cmake b/hw/bsp/kinetis_kl/family.cmake index 230a3057d..b74f4f8b9 100644 --- a/hw/bsp/kinetis_kl/family.cmake +++ b/hw/bsp/kinetis_kl/family.cmake @@ -62,7 +62,7 @@ function(family_configure_example TARGET RTOS) ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/family.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../board.c ${TOP}/src/portable/chipidea/ci_fs/dcd_ci_fs.c - ${TOP}/src/portable/nxp/khci/hcd_khci.c + ${TOP}/src/portable/chipidea/ci_fs/hcd_ci_fs.c ${STARTUP_FILE_${CMAKE_C_COMPILER_ID}} ) target_include_directories(${TARGET} PUBLIC diff --git a/hw/bsp/kinetis_kl/family.mk b/hw/bsp/kinetis_kl/family.mk index 201ab99dc..9c780a868 100644 --- a/hw/bsp/kinetis_kl/family.mk +++ b/hw/bsp/kinetis_kl/family.mk @@ -17,8 +17,8 @@ LDFLAGS += \ -specs=nosys.specs -specs=nano.specs \ SRC_C += \ - src/portable/nxp/khci/dcd_khci.c \ - src/portable/nxp/khci/hcd_khci.c \ + src/portable/chipidea/ci_fs/dcd_ci_fs.c \ + src/portable/chipidea/ci_fs/hcd_ci_fs.c \ $(MCU_DIR)/system_$(MCU).c \ $(MCU_DIR)/drivers/fsl_clock.c \ $(SDK_DIR)/drivers/gpio/fsl_gpio.c \ diff --git a/src/portable/chipidea/ci_fs/hcd_ci_fs.c b/src/portable/chipidea/ci_fs/hcd_ci_fs.c new file mode 100644 index 000000000..44a68a8d6 --- /dev/null +++ b/src/portable/chipidea/ci_fs/hcd_ci_fs.c @@ -0,0 +1,649 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2021 Koji Kitayama + * SPDX-FileCopyrightText: Copyright (c) 2021 Ha Thach (tinyusb.org) + * SPDX-License-Identifier: MIT + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if CFG_TUH_ENABLED && defined(TUP_USBIP_CHIPIDEA_FS) + +#include "host/hcd.h" +#include "host/usbh.h" +#include "ci_fs_type.h" + +// Host is currently only available on NXP Kinetis. The ChipIdea-FS host controller +// interface is register-compatible via ci_fs_regs_t. Unlike the device driver, the host +// driver does not include the ci_fs_.h header because those define the device +// dcd_int_enable()/dcd_int_disable() functions, which would collide in a dual-role build. +#if defined(TUP_USBIP_CHIPIDEA_FS_KINETIS) + #include "fsl_device_registers.h" + #define CI_FS_REG(_port) ((ci_fs_regs_t*) USB0_BASE) + #define CI_FS_IRQN USB0_IRQn +#else + #error "MCU is not supported" +#endif + +#define CI_REG CI_FS_REG(0) + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM DECLARATION +//--------------------------------------------------------------------+ + +enum { + TOK_PID_OUT = 0x1u, + TOK_PID_IN = 0x9u, + TOK_PID_SETUP = 0xDu, + TOK_PID_DATA0 = 0x3u, + TOK_PID_DATA1 = 0xbu, + TOK_PID_ACK = 0x2u, + TOK_PID_STALL = 0xeu, + TOK_PID_NAK = 0xau, + TOK_PID_BUSTO = 0x0u, + TOK_PID_ERR = 0xfu, +}; + +typedef struct TU_ATTR_PACKED +{ + union { + uint32_t head; + struct { + union { + struct { + uint16_t : 2; + __IO uint16_t tok_pid : 4; + uint16_t data : 1; + __IO uint16_t own : 1; + uint16_t : 8; + }; + struct { + uint16_t : 2; + uint16_t bdt_stall : 1; + uint16_t dts : 1; + uint16_t ninc : 1; + uint16_t keep : 1; + uint16_t : 10; + }; + }; + __IO uint16_t bc : 10; + uint16_t : 6; + }; + }; + uint8_t *addr; +}buffer_descriptor_t; + +TU_VERIFY_STATIC( sizeof(buffer_descriptor_t) == 8, "size is not correct" ); + +typedef struct TU_ATTR_PACKED +{ + union { + uint32_t state; + struct { + uint32_t pipenum:16; + uint32_t odd : 1; + uint32_t : 0; + }; + }; + uint8_t *buffer; + uint16_t length; + uint16_t remaining; +} endpoint_state_t; + +typedef struct TU_ATTR_PACKED +{ + uint8_t dev_addr; + uint8_t ep_addr; + uint16_t max_packet_size; + union { + uint8_t flags; + struct { + uint8_t data : 1; + uint8_t xfer : 2; + uint8_t : 0; + }; + }; + uint8_t *buffer; + uint16_t length; + uint16_t remaining; +} pipe_state_t; + + +typedef struct +{ + union { + /* [OUT,IN][EVEN,ODD] */ + buffer_descriptor_t bdt[2][2]; + uint16_t bda[2*2]; + }; + endpoint_state_t endpoint[2]; + pipe_state_t pipe[CFG_TUH_ENDPOINT_MAX * 2]; + uint32_t in_progress; /* Bitmap. Each bit indicates that a transfer of the corresponding pipe is in progress */ + uint32_t pending; /* Bitmap. Each bit indicates that a transfer of the corresponding pipe will be resume the next frame */ + bool need_reset; /* The device has not been reset after connection. */ +} hcd_data_t; + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ +// BDT(Buffer Descriptor Table) must be 256-byte aligned +CFG_TUH_MEM_SECTION TU_ATTR_ALIGNED(512) static hcd_data_t _hcd; +//CFG_TUH_MEM_SECTION TU_ATTR_ALIGNED(4) static uint8_t _rx_buf[1024]; + +static int find_pipe(uint8_t dev_addr, uint8_t ep_addr) +{ + /* Find the target pipe */ + int num; + for (num = 0; num < CFG_TUH_ENDPOINT_MAX * 2; ++num) { + pipe_state_t *p = &_hcd.pipe[num]; + if ((p->dev_addr == dev_addr) && (p->ep_addr == ep_addr)) + return num; + } + return -1; +} + +static int prepare_packets(int pipenum) +{ + pipe_state_t *pipe = &_hcd.pipe[pipenum]; + unsigned const dir_tx = tu_edpt_dir(pipe->ep_addr) ? 0 : 1; + endpoint_state_t *ep = &_hcd.endpoint[dir_tx]; + unsigned const odd = ep->odd; + buffer_descriptor_t *bd = _hcd.bdt[dir_tx]; + // The host shares a single BDT set across all pipes. If it is still owned by an + // in-flight transfer on another pipe, report busy so the caller can defer & retry. + if (bd[odd].own) return -1; + + // TU_LOG1(" %p dir %d odd %d data %d\r\n", &bd[odd], dir_tx, odd, pipe->data); + + ep->pipenum = pipenum; + + bd[odd ].data = pipe->data; + bd[odd ^ 1].data = pipe->data ^ 1; + bd[odd ^ 1].own = 0; + /* reset values for a next transfer */ + + int num_tokens = 0; /* The number of prepared packets */ + unsigned const mps = pipe->max_packet_size; + unsigned const rem = pipe->remaining; + if (rem > mps) { + /* When total_bytes is greater than the max packet size, + * it prepares to the next transfer to avoid NAK in advance. */ + bd[odd ^ 1].bc = rem >= 2 * mps ? mps: rem - mps; + bd[odd ^ 1].addr = pipe->buffer + mps; + bd[odd ^ 1].own = 1; + if (dir_tx) ++num_tokens; + } + bd[odd].bc = rem >= mps ? mps: rem; + bd[odd].addr = pipe->buffer; + __DSB(); + bd[odd].own = 1; /* This bit must be set last */ + ++num_tokens; + return num_tokens; +} + +static int select_next_pipenum(int pipenum) +{ + unsigned wip = _hcd.in_progress & ~_hcd.pending; + if (!wip) return -1; + unsigned msk = TU_GENMASK(31, pipenum); + int next = __builtin_ctz(wip & msk); + if (next) return next; + msk = TU_GENMASK(pipenum, 0); + next = __builtin_ctz(wip & msk); + return next; +} + +/* When transfer is completed, return true. */ +static bool continue_transfer(int pipenum, buffer_descriptor_t *bd) +{ + pipe_state_t *pipe = &_hcd.pipe[pipenum]; + unsigned const bc = bd->bc; + unsigned const rem = pipe->remaining - bc; + + pipe->remaining = rem; + if (rem && bc == pipe->max_packet_size) { + int const next_rem = rem - pipe->max_packet_size; + if (next_rem > 0) { + /* Prepare to the after next transfer */ + bd->addr += pipe->max_packet_size * 2; + bd->bc = next_rem > pipe->max_packet_size ? pipe->max_packet_size: next_rem; + __DSB(); + bd->own = 1; /* This bit must be set last */ + while (CI_REG->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ; + CI_REG->TOKEN = CI_REG->TOKEN; /* Queue the same token as the last */ + } else if (TUSB_DIR_IN == tu_edpt_dir(pipe->ep_addr)) { /* IN */ + while (CI_REG->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ; + CI_REG->TOKEN = CI_REG->TOKEN; + } + return true; + } + pipe->data = bd->data ^ 1; + return false; +} + +static bool resume_transfer(int pipenum) +{ + int num_tokens = prepare_packets(pipenum); + if (num_tokens < 0) { + // Shared BDT still owned by an in-flight transfer on another pipe. Defer this + // pipe and retry on the next SOF once the BDT is free (avoids dropping the + // transfer, which stalls e.g. a 2nd device enumerating behind a hub while the + // app issues concurrent control transfers). + _hcd.pending |= TU_BIT(pipenum); + CI_REG->INT_EN |= USB_ISTAT_SOFTOK_MASK; + return true; + } + + const unsigned ie = NVIC_GetEnableIRQ(CI_FS_IRQN); + NVIC_DisableIRQ(CI_FS_IRQN); + pipe_state_t *pipe = &_hcd.pipe[pipenum]; + + unsigned flags = CI_REG->EP[0].CTL & USB_ENDPT_HOSTWOHUB_MASK; + flags |= USB_ENDPT_EPRXEN_MASK | USB_ENDPT_EPTXEN_MASK; + switch (pipe->xfer) { + case TUSB_XFER_CONTROL: + flags |= USB_ENDPT_EPHSHK_MASK; + break; + case TUSB_XFER_ISOCHRONOUS: + flags |= USB_ENDPT_EPCTLDIS_MASK | USB_ENDPT_RETRYDIS_MASK; + break; + default: + flags |= USB_ENDPT_EPHSHK_MASK | USB_ENDPT_EPCTLDIS_MASK | USB_ENDPT_RETRYDIS_MASK; + break; + } + // TU_LOG1(" resume pipenum %d flags %x\r\n", pipenum, flags); + + CI_REG->EP[0].CTL = flags; + CI_REG->ADDR = (CI_REG->ADDR & USB_ADDR_LSEN_MASK) | pipe->dev_addr; + + unsigned const token = tu_edpt_number(pipe->ep_addr) | + ((tu_edpt_dir(pipe->ep_addr) ? TOK_PID_IN: TOK_PID_OUT) << USB_TOKEN_TOKENPID_SHIFT); + do { + while (CI_REG->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ; + CI_REG->TOKEN = token; + } while (--num_tokens); + if (ie) NVIC_EnableIRQ(CI_FS_IRQN); + return true; +} + +static void suspend_transfer(int pipenum, buffer_descriptor_t *bd) +{ + pipe_state_t *pipe = &_hcd.pipe[pipenum]; + pipe->buffer = bd->addr; + // A NAK transfers no data, so the data toggle must be preserved for the retry. + // (Do NOT flip pipe->data here: flipping it makes the retried packet use the wrong + // DATA0/DATA1, which the device silently discards - breaking any bulk/interrupt + // transfer that is NAKed, e.g. the MSC CBW/CSW when the device is momentarily busy.) + if ((TUSB_XFER_INTERRUPT == pipe->xfer) || + (TUSB_XFER_BULK == pipe->xfer)) { + _hcd.pending |= TU_BIT(pipenum); + CI_REG->INT_EN |= USB_ISTAT_SOFTOK_MASK; + } +} + +static void process_tokdne(uint8_t rhport) +{ + (void)rhport; + const unsigned s = CI_REG->STAT; + CI_REG->INT_STAT = USB_ISTAT_TOKDNE_MASK; /* fetch the next token if received */ + uint8_t const dir_in = (s & USB_STAT_TX_MASK) ? TUSB_DIR_OUT: TUSB_DIR_IN; + unsigned const odd = (s & USB_STAT_ODD_MASK) ? 1 : 0; + + buffer_descriptor_t *bd = (buffer_descriptor_t *)&_hcd.bda[s]; + endpoint_state_t *ep = &_hcd.endpoint[s >> 3]; + + /* fetch status before discarded by the next steps */ + const unsigned pid = bd->tok_pid; + + /* reset values for a next transfer */ + bd->bdt_stall = 0; + bd->dts = 1; + bd->ninc = 0; + bd->keep = 0; + /* Update the odd variable to prepare for the next transfer */ + ep->odd = odd ^ 1; + + int pipenum = ep->pipenum; + int next_pipenum; + // TU_LOG1("TOKDNE %x PID %x pipe %d\r\n", s, pid, pipenum); + + xfer_result_t result; + switch (pid) { + default: + if (continue_transfer(pipenum, bd)) + return; + result = XFER_RESULT_SUCCESS; + break; + case TOK_PID_NAK: + suspend_transfer(pipenum, bd); + next_pipenum = select_next_pipenum(pipenum); + if (0 <= next_pipenum) + resume_transfer(next_pipenum); + return; + case TOK_PID_STALL: + result = XFER_RESULT_STALLED; + break; + case TOK_PID_ERR: /* mismatch toggle bit */ + case TOK_PID_BUSTO: + result = XFER_RESULT_FAILED; + break; + } + _hcd.in_progress &= ~TU_BIT(pipenum); + pipe_state_t *pipe = &_hcd.pipe[ep->pipenum]; + hcd_event_xfer_complete(pipe->dev_addr, + tu_edpt_addr(CI_REG->TOKEN & USB_TOKEN_TOKENENDPT_MASK, dir_in), + pipe->length - pipe->remaining, + result, true); + next_pipenum = select_next_pipenum(pipenum); + if (0 <= next_pipenum) + resume_transfer(next_pipenum); +} + +static void process_attach(uint8_t rhport) +{ + unsigned ctl = CI_REG->CTL; + if (!(ctl & USB_CTL_JSTATE_MASK)) { + /* The attached device is a low speed device. */ + CI_REG->ADDR = USB_ADDR_LSEN_MASK; + CI_REG->EP[0].CTL = USB_ENDPT_HOSTWOHUB_MASK; + } + hcd_event_device_attach(rhport, true); +} + +static void process_bus_reset(uint8_t rhport) +{ + CI_REG->INT_STAT = USB_ISTAT_TOKDNE_MASK; + CI_REG->USBCTRL &= ~USB_USBCTRL_SUSP_MASK; + CI_REG->CTL &= ~USB_CTL_USBENSOFEN_MASK; + CI_REG->ADDR = 0; + CI_REG->EP[0].CTL = 0; + + hcd_event_device_remove(rhport, true); + + _hcd.in_progress = 0; + _hcd.pending = 0; + buffer_descriptor_t *bd = &_hcd.bdt[0][0]; + for (unsigned i = 0; i < 2; ++i, ++bd) { + bd->head = 0; + } +} + +/*------------------------------------------------------------------*/ +/* Host API + *------------------------------------------------------------------*/ +bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { + (void) rhport; + (void) rh_init; + CI_REG->USBTRC0 |= USB_USBTRC0_USBRESET_MASK; + while (CI_REG->USBTRC0 & USB_USBTRC0_USBRESET_MASK); + + tu_memclr(&_hcd, sizeof(_hcd)); + CI_REG->USBTRC0 |= TU_BIT(6); /* software must set this bit to 1 */ + CI_REG->BDT_PAGE1 = (uint8_t)((uintptr_t)_hcd.bdt >> 8); + CI_REG->BDT_PAGE2 = (uint8_t)((uintptr_t)_hcd.bdt >> 16); + CI_REG->BDT_PAGE3 = (uint8_t)((uintptr_t)_hcd.bdt >> 24); + + CI_REG->USBCTRL &= ~USB_USBCTRL_SUSP_MASK; + CI_REG->CTL |= USB_CTL_ODDRST_MASK; + for (unsigned i = 0; i < 16; ++i) { + CI_REG->EP[i].CTL = 0; + } + CI_REG->CTL &= ~USB_CTL_ODDRST_MASK; + + CI_REG->SOF_THLD = 74; /* for 64-byte packets */ + // CI_REG->SOF_THLD = 144; /* for low speed 8-byte packets */ + CI_REG->CTL = USB_CTL_HOSTMODEEN_MASK | USB_CTL_SE0_MASK; + CI_REG->USBCTRL = USB_USBCTRL_PDE_MASK; + + NVIC_ClearPendingIRQ(CI_FS_IRQN); + CI_REG->INT_EN = USB_INTEN_ATTACHEN_MASK | USB_INTEN_TOKDNEEN_MASK | + USB_INTEN_USBRSTEN_MASK | USB_INTEN_ERROREN_MASK | USB_INTEN_STALLEN_MASK; + CI_REG->ERR_ENB = 0xff; + + return true; +} + +void hcd_int_enable(uint8_t rhport) +{ + (void)rhport; + NVIC_EnableIRQ(CI_FS_IRQN); +} + +void hcd_int_disable(uint8_t rhport) +{ + (void)rhport; + NVIC_DisableIRQ(CI_FS_IRQN); +} + +uint32_t hcd_frame_number(uint8_t rhport) +{ + (void)rhport; + /* The device must be reset at least once after connection + * in order to start the frame counter. */ + if (_hcd.need_reset) hcd_port_reset(rhport); + uint32_t frmnum = CI_REG->FRM_NUML; + frmnum |= CI_REG->FRM_NUMH << 8u; + return frmnum; +} + +/*--------------------------------------------------------------------+ + * Port API + *--------------------------------------------------------------------+ */ +bool hcd_port_connect_status(uint8_t rhport) +{ + (void)rhport; + if (CI_REG->INT_STAT & USB_ISTAT_ATTACH_MASK) + return true; + return false; +} + +void hcd_port_reset(uint8_t rhport) +{ + (void)rhport; + CI_REG->CTL &= ~USB_CTL_USBENSOFEN_MASK; + CI_REG->CTL |= USB_CTL_RESET_MASK; + unsigned cnt = SystemCoreClock / 100; + while (cnt--) __NOP(); + CI_REG->CTL &= ~USB_CTL_RESET_MASK; + CI_REG->CTL |= USB_CTL_USBENSOFEN_MASK; + _hcd.need_reset = false; +} + +void hcd_port_reset_end(uint8_t rhport) { + (void) rhport; +} + +tusb_speed_t hcd_port_speed_get(uint8_t rhport) +{ + (void)rhport; + tusb_speed_t speed = TUSB_SPEED_FULL; + const unsigned ie = NVIC_GetEnableIRQ(CI_FS_IRQN); + NVIC_DisableIRQ(CI_FS_IRQN); + if (CI_REG->ADDR & USB_ADDR_LSEN_MASK) + speed = TUSB_SPEED_LOW; + if (ie) NVIC_EnableIRQ(CI_FS_IRQN); + return speed; +} + +void hcd_device_close(uint8_t rhport, uint8_t dev_addr) +{ + (void)rhport; + const unsigned ie = NVIC_GetEnableIRQ(CI_FS_IRQN); + NVIC_DisableIRQ(CI_FS_IRQN); + pipe_state_t *p = &_hcd.pipe[0]; + pipe_state_t *end = &_hcd.pipe[CFG_TUH_ENDPOINT_MAX * 2]; + for (;p != end; ++p) { + if (p->dev_addr == dev_addr) + tu_memclr(p, sizeof(*p)); + } + if (ie) NVIC_EnableIRQ(CI_FS_IRQN); +} + +//--------------------------------------------------------------------+ +// Endpoints API +//--------------------------------------------------------------------+ +bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8]) +{ + (void)rhport; + // TU_LOG1("SETUP %u\r\n", dev_addr); + TU_ASSERT(0 == (_hcd.in_progress & TU_BIT(0))); + + int pipenum = find_pipe(dev_addr, 0); + if (pipenum < 0) return false; + + pipe_state_t *pipe = &_hcd.pipe[pipenum]; + pipe[0].data = 0; + pipe[0].buffer = (uint8_t*)(uintptr_t)setup_packet; + pipe[0].length = 8; + pipe[0].remaining = 8; + pipe[1].data = 1; + + if (1 != prepare_packets(pipenum)) + return false; + + _hcd.in_progress |= TU_BIT(pipenum); + + unsigned hostwohub = CI_REG->EP[0].CTL & USB_ENDPT_HOSTWOHUB_MASK; + CI_REG->EP[0].CTL = hostwohub | + USB_ENDPT_EPHSHK_MASK | USB_ENDPT_EPRXEN_MASK | USB_ENDPT_EPTXEN_MASK; + CI_REG->ADDR = (CI_REG->ADDR & USB_ADDR_LSEN_MASK) | dev_addr; + while (CI_REG->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ; + CI_REG->TOKEN = (TOK_PID_SETUP << USB_TOKEN_TOKENPID_SHIFT); + return true; +} + +bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc) +{ + (void)rhport; + uint8_t const ep_addr = ep_desc->bEndpointAddress; + // TU_LOG1("O %u %x\r\n", dev_addr, ep_addr); + /* Find a free pipe */ + pipe_state_t *p = &_hcd.pipe[0]; + pipe_state_t *end = &_hcd.pipe[CFG_TUH_ENDPOINT_MAX * 2]; + if (dev_addr || ep_addr) { + p += 2; + for (; p < end && (p->dev_addr || p->ep_addr); ++p) ; + if (p == end) return false; + } + p->dev_addr = dev_addr; + p->ep_addr = ep_addr; + p->max_packet_size = ep_desc->wMaxPacketSize; + p->xfer = ep_desc->bmAttributes.xfer; + p->data = 0; + if (!ep_addr) { + /* Open one more pipe for Control IN transfer */ + TU_ASSERT(TUSB_XFER_CONTROL == p->xfer); + pipe_state_t *q = p + 1; + TU_ASSERT(!q->dev_addr && !q->ep_addr); + q->dev_addr = dev_addr; + q->ep_addr = tu_edpt_addr(0, TUSB_DIR_IN); + q->max_packet_size = ep_desc->wMaxPacketSize; + q->xfer = ep_desc->bmAttributes.xfer; + q->data = 1; + } + return true; +} + +bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { + (void) rhport; (void) daddr; (void) ep_addr; + return false; // TODO not implemented yet +} + +/* The address of buffer must be aligned to 4 byte boundary. And it must be at least 4 bytes long. + * DMA writes data in 4 byte unit */ +bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen) +{ + (void)rhport; + // TU_LOG1("X %u %x %x %d\r\n", dev_addr, ep_addr, (uintptr_t)buffer, buflen); + + int pipenum = find_pipe(dev_addr, ep_addr); + TU_ASSERT(0 <= pipenum); + + TU_ASSERT(0 == (_hcd.in_progress & TU_BIT(pipenum))); + unsigned const ie = NVIC_GetEnableIRQ(CI_FS_IRQN); + NVIC_DisableIRQ(CI_FS_IRQN); + pipe_state_t *pipe = &_hcd.pipe[pipenum]; + pipe->buffer = buffer; + pipe->length = buflen; + pipe->remaining = buflen; + _hcd.in_progress |= TU_BIT(pipenum); + _hcd.pending |= TU_BIT(pipenum); /* Send at the next Frame */ + CI_REG->INT_EN |= USB_ISTAT_SOFTOK_MASK; + if (ie) NVIC_EnableIRQ(CI_FS_IRQN); + return true; +} + +bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { + (void) rhport; + (void) dev_addr; + (void) ep_addr; + // TODO not implemented yet + return false; +} + +bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { + (void) rhport; + if (!tu_edpt_number(ep_addr)) return true; + int num = find_pipe(dev_addr, ep_addr); + if (num < 0) return false; + pipe_state_t *p = &_hcd.pipe[num]; + p->data = 0; /* Reset data toggle */ + return true; +} + +/*--------------------------------------------------------------------+ + * ISR + *--------------------------------------------------------------------+*/ +void hcd_int_handler(uint8_t rhport, bool in_isr) +{ + (void) in_isr; + uint32_t is = CI_REG->INT_STAT; + uint32_t msk = CI_REG->INT_EN; + + // TU_LOG1("S %lx\r\n", is); + + /* clear disabled interrupts */ + CI_REG->INT_STAT = (is & ~msk & ~USB_ISTAT_TOKDNE_MASK) | USB_ISTAT_SOFTOK_MASK; + is &= msk; + + if (is & USB_ISTAT_ERROR_MASK) { + unsigned err = CI_REG->ERR_STAT; + if (err) { + TU_LOG1(" ERR %x\r\n", err); + CI_REG->ERR_STAT = err; + } else { + CI_REG->INT_EN &= ~USB_ISTAT_ERROR_MASK; + } + } + + if (is & USB_ISTAT_USBRST_MASK) { + CI_REG->INT_EN = (msk & ~USB_INTEN_USBRSTEN_MASK) | USB_INTEN_ATTACHEN_MASK; + process_bus_reset(rhport); + return; + } + if (is & USB_ISTAT_ATTACH_MASK) { + CI_REG->INT_EN = (msk & ~USB_INTEN_ATTACHEN_MASK) | USB_INTEN_USBRSTEN_MASK; + _hcd.need_reset = true; + process_attach(rhport); + return; + } + if (is & USB_ISTAT_STALL_MASK) { + CI_REG->INT_STAT = USB_ISTAT_STALL_MASK; + } + if (is & USB_ISTAT_SOFTOK_MASK) { + msk &= ~USB_ISTAT_SOFTOK_MASK; + CI_REG->INT_EN = msk; + if (_hcd.pending) { + int pipenum = __builtin_ctz(_hcd.pending); + _hcd.pending = 0; + if (!(is & USB_ISTAT_TOKDNE_MASK)) + resume_transfer(pipenum); + } + } + if (is & USB_ISTAT_TOKDNE_MASK) { + process_tokdne(rhport); + } +} + +#endif diff --git a/src/portable/nxp/khci/dcd_khci.c b/src/portable/nxp/khci/dcd_khci.c deleted file mode 100644 index 9f61bd236..000000000 --- a/src/portable/nxp/khci/dcd_khci.c +++ /dev/null @@ -1,560 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2020 Koji Kitayama - * SPDX-FileCopyrightText: Copyright (c) 2020 Ha Thach (tinyusb.org) - * SPDX-License-Identifier: MIT - * - * This file is part of the TinyUSB stack. - */ - -#include "tusb_option.h" - -#if CFG_TUD_ENABLED && defined(TUP_USBIP_CHIPIDEA_FS) - -#ifdef TUP_USBIP_CHIPIDEA_FS_KINETIS - #include "fsl_device_registers.h" - #define KHCI USB0 -#else - #error "MCU is not supported" -#endif - -#include "device/dcd.h" - -//--------------------------------------------------------------------+ -// MACRO TYPEDEF CONSTANT ENUM DECLARATION -//--------------------------------------------------------------------+ - -enum { - TOK_PID_OUT = 0x1u, - TOK_PID_IN = 0x9u, - TOK_PID_SETUP = 0xDu, -}; - -typedef struct TU_ATTR_PACKED -{ - union { - uint32_t head; - struct { - union { - struct { - uint16_t : 2; - __IO uint16_t tok_pid : 4; - uint16_t data : 1; - __IO uint16_t own : 1; - uint16_t : 8; - }; - struct { - uint16_t : 2; - uint16_t bdt_stall : 1; - uint16_t dts : 1; - uint16_t ninc : 1; - uint16_t keep : 1; - uint16_t : 10; - }; - }; - __IO uint16_t bc : 10; - uint16_t : 6; - }; - }; - uint8_t *addr; -}buffer_descriptor_t; - -TU_VERIFY_STATIC( sizeof(buffer_descriptor_t) == 8, "size is not correct" ); - -typedef struct TU_ATTR_PACKED -{ - union { - uint32_t state; - struct { - uint32_t max_packet_size :11; - uint32_t : 5; - uint32_t odd : 1; - uint32_t :15; - }; - }; - uint16_t length; - uint16_t remaining; -}endpoint_state_t; - -TU_VERIFY_STATIC( sizeof(endpoint_state_t) == 8, "size is not correct" ); - -typedef struct -{ - union { - /* [#EP][OUT,IN][EVEN,ODD] */ - buffer_descriptor_t bdt[16][2][2]; - uint16_t bda[512]; - }; - TU_ATTR_ALIGNED(4) union { - endpoint_state_t endpoint[16][2]; - endpoint_state_t endpoint_unified[16 * 2]; - }; - uint8_t setup_packet[8]; - uint8_t addr; -}dcd_data_t; - -//--------------------------------------------------------------------+ -// INTERNAL OBJECT & FUNCTION DECLARATION -//--------------------------------------------------------------------+ -// BDT(Buffer Descriptor Table) must be 256-byte aligned -CFG_TUD_MEM_SECTION TU_ATTR_ALIGNED(512) static dcd_data_t _dcd; - -TU_VERIFY_STATIC( sizeof(_dcd.bdt) == 512, "size is not correct" ); - -static void prepare_next_setup_packet(uint8_t rhport) -{ - const unsigned out_odd = _dcd.endpoint[0][0].odd; - const unsigned in_odd = _dcd.endpoint[0][1].odd; - TU_ASSERT(0 == _dcd.bdt[0][0][out_odd].own, ); - - _dcd.bdt[0][0][out_odd].data = 0; - _dcd.bdt[0][0][out_odd ^ 1].data = 1; - _dcd.bdt[0][1][in_odd].data = 1; - _dcd.bdt[0][1][in_odd ^ 1].data = 0; - dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_OUT), - _dcd.setup_packet, sizeof(_dcd.setup_packet), false); -} - -static void process_stall(uint8_t rhport) -{ - for (int i = 0; i < 16; ++i) { - unsigned const endpt = KHCI->ENDPOINT[i].ENDPT; - - if (endpt & USB_ENDPT_EPSTALL_MASK) { - // prepare next setup if endpoint0 - if ( i == 0 ) prepare_next_setup_packet(rhport); - - // clear stall bit - KHCI->ENDPOINT[i].ENDPT = endpt & ~USB_ENDPT_EPSTALL_MASK; - } - } -} - -static void process_tokdne(uint8_t rhport) -{ - const unsigned s = KHCI->STAT; - KHCI->ISTAT = USB_ISTAT_TOKDNE_MASK; /* fetch the next token if received */ - - uint8_t const epnum = (s >> USB_STAT_ENDP_SHIFT); - uint8_t const dir = (s & USB_STAT_TX_MASK) >> USB_STAT_TX_SHIFT; - unsigned const odd = (s & USB_STAT_ODD_MASK) ? 1 : 0; - - buffer_descriptor_t *bd = (buffer_descriptor_t *)&_dcd.bda[s]; - endpoint_state_t *ep = &_dcd.endpoint_unified[s >> 3]; - - /* fetch pid before discarded by the next steps */ - const unsigned pid = bd->tok_pid; - - /* reset values for a next transfer */ - bd->bdt_stall = 0; - bd->dts = 1; - bd->ninc = 0; - bd->keep = 0; - /* update the odd variable to prepare for the next transfer */ - ep->odd = odd ^ 1; - if (pid == TOK_PID_SETUP) { - dcd_event_setup_received(rhport, bd->addr, true); - KHCI->CTL &= ~USB_CTL_TXSUSPENDTOKENBUSY_MASK; - return; - } - - const unsigned bc = bd->bc; - const unsigned remaining = ep->remaining - bc; - if (remaining && bc == ep->max_packet_size) { - /* continue the transferring consecutive data */ - ep->remaining = remaining; - const int next_remaining = remaining - ep->max_packet_size; - if (next_remaining > 0) { - /* prepare to the after next transfer */ - bd->addr += ep->max_packet_size * 2; - bd->bc = next_remaining > ep->max_packet_size ? ep->max_packet_size: next_remaining; - __DSB(); - bd->own = 1; /* the own bit must set after addr */ - } - return; - } - const unsigned length = ep->length; - dcd_event_xfer_complete(rhport, - tu_edpt_addr(epnum, dir), - length - remaining, XFER_RESULT_SUCCESS, true); - if (0 == epnum && 0 == length) { - /* After completion a ZLP of control transfer, - * it prepares for the next steup transfer. */ - if (_dcd.addr) { - /* When the transfer was the SetAddress, - * the device address should be updated here. */ - KHCI->ADDR = _dcd.addr; - _dcd.addr = 0; - } - prepare_next_setup_packet(rhport); - } -} - -static void process_bus_reset(uint8_t rhport) -{ - KHCI->USBCTRL &= ~USB_USBCTRL_SUSP_MASK; - KHCI->CTL |= USB_CTL_ODDRST_MASK; - KHCI->ADDR = 0; - KHCI->INTEN = USB_INTEN_USBRSTEN_MASK | USB_INTEN_TOKDNEEN_MASK | USB_INTEN_SLEEPEN_MASK | - USB_INTEN_ERROREN_MASK | USB_INTEN_STALLEN_MASK; - - KHCI->ENDPOINT[0].ENDPT = USB_ENDPT_EPHSHK_MASK | USB_ENDPT_EPRXEN_MASK | USB_ENDPT_EPTXEN_MASK; - for (unsigned i = 1; i < 16; ++i) { - KHCI->ENDPOINT[i].ENDPT = 0; - } - buffer_descriptor_t *bd = _dcd.bdt[0][0]; - for (unsigned i = 0; i < sizeof(_dcd.bdt)/sizeof(*bd); ++i, ++bd) { - bd->head = 0; - } - const endpoint_state_t ep0 = { - .max_packet_size = CFG_TUD_ENDPOINT0_SIZE, - .odd = 0, - .length = 0, - .remaining = 0, - }; - _dcd.endpoint[0][0] = ep0; - _dcd.endpoint[0][1] = ep0; - tu_memclr(_dcd.endpoint[1], sizeof(_dcd.endpoint) - sizeof(_dcd.endpoint[0])); - _dcd.addr = 0; - prepare_next_setup_packet(rhport); - KHCI->CTL &= ~USB_CTL_ODDRST_MASK; - dcd_event_bus_reset(rhport, TUSB_SPEED_FULL, true); -} - -static void process_bus_sleep(uint8_t rhport) -{ - // Enable resume & disable suspend interrupt - const unsigned inten = KHCI->INTEN; - - KHCI->INTEN = (inten & ~USB_INTEN_SLEEPEN_MASK) | USB_INTEN_RESUMEEN_MASK; - KHCI->USBTRC0 |= USB_USBTRC0_USBRESMEN_MASK; - KHCI->USBCTRL |= USB_USBCTRL_SUSP_MASK; - - dcd_event_bus_signal(rhport, DCD_EVENT_SUSPEND, true); -} - -static void process_bus_resume(uint8_t rhport) -{ - // Enable suspend & disable resume interrupt - const unsigned inten = KHCI->INTEN; - - KHCI->USBCTRL &= ~USB_USBCTRL_SUSP_MASK; // will also clear USB_USBTRC0_USB_RESUME_INT_MASK - KHCI->USBTRC0 &= ~USB_USBTRC0_USBRESMEN_MASK; - KHCI->INTEN = (inten & ~USB_INTEN_RESUMEEN_MASK) | USB_INTEN_SLEEPEN_MASK; - - dcd_event_bus_signal(rhport, DCD_EVENT_RESUME, true); -} - -/*------------------------------------------------------------------*/ -/* Device API - *------------------------------------------------------------------*/ -bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { - (void) rhport; - (void) rh_init; - - // save crystal-less setting (if available) - #if defined(FSL_FEATURE_USB_KHCI_IRC48M_MODULE_CLOCK_ENABLED) && FSL_FEATURE_USB_KHCI_IRC48M_MODULE_CLOCK_ENABLED == 1 - uint32_t clk_recover_irc_en = KHCI->CLK_RECOVER_IRC_EN; - uint32_t clk_recover_ctrl = KHCI->CLK_RECOVER_CTRL; - #endif - - KHCI->USBTRC0 |= USB_USBTRC0_USBRESET_MASK; - while (KHCI->USBTRC0 & USB_USBTRC0_USBRESET_MASK); - - // restore crystal-less setting (if available) - #if defined(FSL_FEATURE_USB_KHCI_IRC48M_MODULE_CLOCK_ENABLED) && FSL_FEATURE_USB_KHCI_IRC48M_MODULE_CLOCK_ENABLED == 1 - KHCI->CLK_RECOVER_IRC_EN = clk_recover_irc_en; - KHCI->CLK_RECOVER_CTRL |= clk_recover_ctrl; - #endif - - tu_memclr(&_dcd, sizeof(_dcd)); - KHCI->USBTRC0 |= TU_BIT(6); /* software must set this bit to 1 */ - KHCI->BDTPAGE1 = (uint8_t)((uintptr_t)_dcd.bdt >> 8); - KHCI->BDTPAGE2 = (uint8_t)((uintptr_t)_dcd.bdt >> 16); - KHCI->BDTPAGE3 = (uint8_t)((uintptr_t)_dcd.bdt >> 24); - - KHCI->INTEN = USB_INTEN_USBRSTEN_MASK; - - dcd_connect(rhport); - NVIC_ClearPendingIRQ(USB0_IRQn); - - return true; -} - -void dcd_int_enable(uint8_t rhport) -{ - (void) rhport; - NVIC_EnableIRQ(USB0_IRQn); -} - -void dcd_int_disable(uint8_t rhport) -{ - (void) rhport; - NVIC_DisableIRQ(USB0_IRQn); -} - -void dcd_set_address(uint8_t rhport, uint8_t dev_addr) -{ - _dcd.addr = dev_addr & 0x7F; - /* Response with status first before changing device address */ - dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0, false); -} - -void dcd_remote_wakeup(uint8_t rhport) -{ - (void) rhport; - - KHCI->CTL |= USB_CTL_RESUME_MASK; - - unsigned cnt = SystemCoreClock / 1000; - while (cnt--) __NOP(); - - KHCI->CTL &= ~USB_CTL_RESUME_MASK; -} - -void dcd_connect(uint8_t rhport) -{ - (void) rhport; - KHCI->USBCTRL = 0; - KHCI->CONTROL |= USB_CONTROL_DPPULLUPNONOTG_MASK; - KHCI->CTL |= USB_CTL_USBENSOFEN_MASK; -} - -void dcd_disconnect(uint8_t rhport) -{ - (void) rhport; - KHCI->CTL = 0; - KHCI->CONTROL &= ~USB_CONTROL_DPPULLUPNONOTG_MASK; -} - -void dcd_sof_enable(uint8_t rhport, bool en) -{ - (void) rhport; - (void) en; - - // TODO implement later -} - -//--------------------------------------------------------------------+ -// Endpoint API -//--------------------------------------------------------------------+ -static bool edpt_open(uint8_t rhport, uint8_t ep_addr, uint16_t max_packet_size, tusb_xfer_type_t xfer) { - (void)rhport; - - const unsigned epn = tu_edpt_number(ep_addr); - const unsigned dir = tu_edpt_dir(ep_addr); - endpoint_state_t *ep = &_dcd.endpoint[epn][dir]; - const unsigned odd = ep->odd; - buffer_descriptor_t *bd = _dcd.bdt[epn][dir]; - - /* No support for control transfer */ - TU_ASSERT(epn && (xfer != TUSB_XFER_CONTROL)); - - ep->max_packet_size = max_packet_size; - unsigned val = USB_ENDPT_EPCTLDIS_MASK; - val |= (xfer != TUSB_XFER_ISOCHRONOUS) ? USB_ENDPT_EPHSHK_MASK : 0; - val |= dir ? USB_ENDPT_EPTXEN_MASK : USB_ENDPT_EPRXEN_MASK; - KHCI->ENDPOINT[epn].ENDPT |= val; - - if (xfer != TUSB_XFER_ISOCHRONOUS) { - bd[odd].dts = 1; - bd[odd].data = 0; - bd[odd ^ 1].dts = 1; - bd[odd ^ 1].data = 1; - } - - return true; -} - -bool dcd_edpt_open(uint8_t rhport, const tusb_desc_endpoint_t *ep_desc) { - return edpt_open(rhport, ep_desc->bEndpointAddress, tu_edpt_packet_size(ep_desc), ep_desc->bmAttributes.xfer); -} - -bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { - return edpt_open(rhport, ep_addr, largest_packet_size, TUSB_XFER_ISOCHRONOUS); -} - -bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *ep_desc) { - const unsigned epn = tu_edpt_number(ep_desc->bEndpointAddress); - const unsigned dir = tu_edpt_dir(ep_desc->bEndpointAddress); - endpoint_state_t *ep = &_dcd.endpoint[epn][dir]; - - dcd_int_disable(rhport); - ep->max_packet_size = tu_edpt_packet_size(ep_desc); - dcd_int_enable(rhport); - - return true; -} - -void dcd_edpt_close_all(uint8_t rhport) -{ - (void) rhport; - const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn); - NVIC_DisableIRQ(USB0_IRQn); - for (unsigned i = 1; i < 16; ++i) { - KHCI->ENDPOINT[i].ENDPT = 0; - } - if (ie) NVIC_EnableIRQ(USB0_IRQn); - buffer_descriptor_t *bd = _dcd.bdt[1][0]; - for (unsigned i = 2; i < sizeof(_dcd.bdt)/sizeof(*bd); ++i, ++bd) { - bd->head = 0; - } - endpoint_state_t *ep = &_dcd.endpoint[1][0]; - for (unsigned i = 2; i < sizeof(_dcd.endpoint)/sizeof(*ep); ++i, ++ep) { - /* Clear except the odd */ - ep->max_packet_size = 0; - ep->length = 0; - ep->remaining = 0; - } -} - -bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr) -{ - (void) rhport; - (void) is_isr; - const unsigned epn = tu_edpt_number(ep_addr); - const unsigned dir = tu_edpt_dir(ep_addr); - endpoint_state_t *ep = &_dcd.endpoint[epn][dir]; - buffer_descriptor_t *bd = &_dcd.bdt[epn][dir][ep->odd]; - TU_ASSERT(0 == bd->own); - - const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn); - NVIC_DisableIRQ(USB0_IRQn); - - ep->length = total_bytes; - ep->remaining = total_bytes; - - const unsigned mps = ep->max_packet_size; - if (total_bytes > mps) { - buffer_descriptor_t *next = ep->odd ? bd - 1: bd + 1; - /* When total_bytes is greater than the max packet size, - * it prepares to the next transfer to avoid NAK in advance. */ - next->bc = total_bytes >= 2 * mps ? mps: total_bytes - mps; - next->addr = buffer + mps; - next->own = 1; - } - bd->bc = total_bytes >= mps ? mps: total_bytes; - bd->addr = buffer; - __DSB(); - bd->own = 1; /* This bit must be set last */ - - if (ie) NVIC_EnableIRQ(USB0_IRQn); - return true; -} - -void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - const unsigned epn = tu_edpt_number(ep_addr); - - if (0 == epn) { - KHCI->ENDPOINT[epn].ENDPT |= USB_ENDPT_EPSTALL_MASK; - } else { - const unsigned dir = tu_edpt_dir(ep_addr); - const unsigned odd = _dcd.endpoint[epn][dir].odd; - buffer_descriptor_t *bd = &_dcd.bdt[epn][dir][odd]; - TU_ASSERT(0 == bd->own,); - - const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn); - NVIC_DisableIRQ(USB0_IRQn); - - bd->bdt_stall = 1; - __DSB(); - bd->own = 1; /* This bit must be set last */ - - if (ie) NVIC_EnableIRQ(USB0_IRQn); - } -} - -void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - const unsigned epn = tu_edpt_number(ep_addr); - TU_VERIFY(epn,); - const unsigned dir = tu_edpt_dir(ep_addr); - const unsigned odd = _dcd.endpoint[epn][dir].odd; - buffer_descriptor_t *bd = _dcd.bdt[epn][dir]; - TU_VERIFY(bd[odd].own,); - - const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn); - NVIC_DisableIRQ(USB0_IRQn); - - bd[odd].own = 0; - __DSB(); - - // clear stall - bd[odd].bdt_stall = 0; - - // Reset data toggle - bd[odd ].data = 0; - bd[odd ^ 1].data = 1; - - // We already cleared this in ISR, but just clear it here to be safe - const unsigned endpt = KHCI->ENDPOINT[epn].ENDPT; - if (endpt & USB_ENDPT_EPSTALL_MASK) { - KHCI->ENDPOINT[epn].ENDPT = endpt & ~USB_ENDPT_EPSTALL_MASK; - } - - if (ie) NVIC_EnableIRQ(USB0_IRQn); -} - -//--------------------------------------------------------------------+ -// ISR -//--------------------------------------------------------------------+ -void dcd_int_handler(uint8_t rhport) -{ - uint32_t is = KHCI->ISTAT; - uint32_t msk = KHCI->INTEN; - - // clear non-enabled interrupts - KHCI->ISTAT = is & ~msk; - is &= msk; - - if (is & USB_ISTAT_ERROR_MASK) { - /* TODO: */ - uint32_t es = KHCI->ERRSTAT; - KHCI->ERRSTAT = es; - KHCI->ISTAT = is; /* discard any pending events */ - } - - if (is & USB_ISTAT_USBRST_MASK) { - KHCI->ISTAT = is; /* discard any pending events */ - process_bus_reset(rhport); - } - - if (is & USB_ISTAT_SLEEP_MASK) { - // TU_LOG3("Suspend: "); TU_LOG2_HEX(is); - - // Note Host usually has extra delay after bus reset (without SOF), which could falsely - // detected as Sleep event. Though usbd has debouncing logic so we are good - KHCI->ISTAT = USB_ISTAT_SLEEP_MASK; - process_bus_sleep(rhport); - } - -#if 0 // ISTAT_RESUME never trigger, probably for host mode ? - if (is & USB_ISTAT_RESUME_MASK) { - // TU_LOG2("ISTAT Resume: "); TU_LOG2_HEX(is); - KHCI->ISTAT = USB_ISTAT_RESUME_MASK; - process_bus_resume(rhport); - } -#endif - - if (KHCI->USBTRC0 & USB_USBTRC0_USB_RESUME_INT_MASK) { - // TU_LOG2("USBTRC0 Resume: "); TU_LOG2_HEX(is); TU_LOG2_HEX(KHCI->USBTRC0); - process_bus_resume(rhport); - } - - if (is & USB_ISTAT_SOFTOK_MASK) { - KHCI->ISTAT = USB_ISTAT_SOFTOK_MASK; - dcd_event_sof(rhport, tu_u16(KHCI->FRMNUMH, KHCI->FRMNUML), true); - } - - if (is & USB_ISTAT_STALL_MASK) { - KHCI->ISTAT = USB_ISTAT_STALL_MASK; - process_stall(rhport); - } - - if (is & USB_ISTAT_TOKDNE_MASK) { - process_tokdne(rhport); - } -} -#endif diff --git a/src/portable/nxp/khci/hcd_khci.c b/src/portable/nxp/khci/hcd_khci.c deleted file mode 100644 index 209940656..000000000 --- a/src/portable/nxp/khci/hcd_khci.c +++ /dev/null @@ -1,628 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2021 Koji Kitayama - * SPDX-FileCopyrightText: Copyright (c) 2021 Ha Thach (tinyusb.org) - * SPDX-License-Identifier: MIT - * - * This file is part of the TinyUSB stack. - */ - -#include "tusb_option.h" - -#if CFG_TUH_ENABLED && defined(TUP_USBIP_CHIPIDEA_FS) - -#ifdef TUP_USBIP_CHIPIDEA_FS_KINETIS - #include "fsl_device_registers.h" - #define KHCI USB0 -#else - #error "MCU is not supported" -#endif - -#include "host/hcd.h" -#include "host/usbh.h" - -//--------------------------------------------------------------------+ -// MACRO TYPEDEF CONSTANT ENUM DECLARATION -//--------------------------------------------------------------------+ - -enum { - TOK_PID_OUT = 0x1u, - TOK_PID_IN = 0x9u, - TOK_PID_SETUP = 0xDu, - TOK_PID_DATA0 = 0x3u, - TOK_PID_DATA1 = 0xbu, - TOK_PID_ACK = 0x2u, - TOK_PID_STALL = 0xeu, - TOK_PID_NAK = 0xau, - TOK_PID_BUSTO = 0x0u, - TOK_PID_ERR = 0xfu, -}; - -typedef struct TU_ATTR_PACKED -{ - union { - uint32_t head; - struct { - union { - struct { - uint16_t : 2; - __IO uint16_t tok_pid : 4; - uint16_t data : 1; - __IO uint16_t own : 1; - uint16_t : 8; - }; - struct { - uint16_t : 2; - uint16_t bdt_stall : 1; - uint16_t dts : 1; - uint16_t ninc : 1; - uint16_t keep : 1; - uint16_t : 10; - }; - }; - __IO uint16_t bc : 10; - uint16_t : 6; - }; - }; - uint8_t *addr; -}buffer_descriptor_t; - -TU_VERIFY_STATIC( sizeof(buffer_descriptor_t) == 8, "size is not correct" ); - -typedef struct TU_ATTR_PACKED -{ - union { - uint32_t state; - struct { - uint32_t pipenum:16; - uint32_t odd : 1; - uint32_t : 0; - }; - }; - uint8_t *buffer; - uint16_t length; - uint16_t remaining; -} endpoint_state_t; - -typedef struct TU_ATTR_PACKED -{ - uint8_t dev_addr; - uint8_t ep_addr; - uint16_t max_packet_size; - union { - uint8_t flags; - struct { - uint8_t data : 1; - uint8_t xfer : 2; - uint8_t : 0; - }; - }; - uint8_t *buffer; - uint16_t length; - uint16_t remaining; -} pipe_state_t; - - -typedef struct -{ - union { - /* [OUT,IN][EVEN,ODD] */ - buffer_descriptor_t bdt[2][2]; - uint16_t bda[2*2]; - }; - endpoint_state_t endpoint[2]; - pipe_state_t pipe[CFG_TUH_ENDPOINT_MAX * 2]; - uint32_t in_progress; /* Bitmap. Each bit indicates that a transfer of the corresponding pipe is in progress */ - uint32_t pending; /* Bitmap. Each bit indicates that a transfer of the corresponding pipe will be resume the next frame */ - bool need_reset; /* The device has not been reset after connection. */ -} hcd_data_t; - -//--------------------------------------------------------------------+ -// INTERNAL OBJECT & FUNCTION DECLARATION -//--------------------------------------------------------------------+ -// BDT(Buffer Descriptor Table) must be 256-byte aligned -CFG_TUH_MEM_SECTION TU_ATTR_ALIGNED(512) static hcd_data_t _hcd; -//CFG_TUH_MEM_SECTION TU_ATTR_ALIGNED(4) static uint8_t _rx_buf[1024]; - -static int find_pipe(uint8_t dev_addr, uint8_t ep_addr) -{ - /* Find the target pipe */ - int num; - for (num = 0; num < CFG_TUH_ENDPOINT_MAX * 2; ++num) { - pipe_state_t *p = &_hcd.pipe[num]; - if ((p->dev_addr == dev_addr) && (p->ep_addr == ep_addr)) - return num; - } - return -1; -} - -static int prepare_packets(int pipenum) -{ - pipe_state_t *pipe = &_hcd.pipe[pipenum]; - unsigned const dir_tx = tu_edpt_dir(pipe->ep_addr) ? 0 : 1; - endpoint_state_t *ep = &_hcd.endpoint[dir_tx]; - unsigned const odd = ep->odd; - buffer_descriptor_t *bd = _hcd.bdt[dir_tx]; - TU_ASSERT(0 == bd[odd].own, -1); - - // TU_LOG1(" %p dir %d odd %d data %d\r\n", &bd[odd], dir_tx, odd, pipe->data); - - ep->pipenum = pipenum; - - bd[odd ].data = pipe->data; - bd[odd ^ 1].data = pipe->data ^ 1; - bd[odd ^ 1].own = 0; - /* reset values for a next transfer */ - - int num_tokens = 0; /* The number of prepared packets */ - unsigned const mps = pipe->max_packet_size; - unsigned const rem = pipe->remaining; - if (rem > mps) { - /* When total_bytes is greater than the max packet size, - * it prepares to the next transfer to avoid NAK in advance. */ - bd[odd ^ 1].bc = rem >= 2 * mps ? mps: rem - mps; - bd[odd ^ 1].addr = pipe->buffer + mps; - bd[odd ^ 1].own = 1; - if (dir_tx) ++num_tokens; - } - bd[odd].bc = rem >= mps ? mps: rem; - bd[odd].addr = pipe->buffer; - __DSB(); - bd[odd].own = 1; /* This bit must be set last */ - ++num_tokens; - return num_tokens; -} - -static int select_next_pipenum(int pipenum) -{ - unsigned wip = _hcd.in_progress & ~_hcd.pending; - if (!wip) return -1; - unsigned msk = TU_GENMASK(31, pipenum); - int next = __builtin_ctz(wip & msk); - if (next) return next; - msk = TU_GENMASK(pipenum, 0); - next = __builtin_ctz(wip & msk); - return next; -} - -/* When transfer is completed, return true. */ -static bool continue_transfer(int pipenum, buffer_descriptor_t *bd) -{ - pipe_state_t *pipe = &_hcd.pipe[pipenum]; - unsigned const bc = bd->bc; - unsigned const rem = pipe->remaining - bc; - - pipe->remaining = rem; - if (rem && bc == pipe->max_packet_size) { - int const next_rem = rem - pipe->max_packet_size; - if (next_rem > 0) { - /* Prepare to the after next transfer */ - bd->addr += pipe->max_packet_size * 2; - bd->bc = next_rem > pipe->max_packet_size ? pipe->max_packet_size: next_rem; - __DSB(); - bd->own = 1; /* This bit must be set last */ - while (KHCI->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ; - KHCI->TOKEN = KHCI->TOKEN; /* Queue the same token as the last */ - } else if (TUSB_DIR_IN == tu_edpt_dir(pipe->ep_addr)) { /* IN */ - while (KHCI->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ; - KHCI->TOKEN = KHCI->TOKEN; - } - return true; - } - pipe->data = bd->data ^ 1; - return false; -} - -static bool resume_transfer(int pipenum) -{ - int num_tokens = prepare_packets(pipenum); - TU_ASSERT(0 <= num_tokens); - - const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn); - NVIC_DisableIRQ(USB0_IRQn); - pipe_state_t *pipe = &_hcd.pipe[pipenum]; - - unsigned flags = KHCI->ENDPOINT[0].ENDPT & USB_ENDPT_HOSTWOHUB_MASK; - flags |= USB_ENDPT_EPRXEN_MASK | USB_ENDPT_EPTXEN_MASK; - switch (pipe->xfer) { - case TUSB_XFER_CONTROL: - flags |= USB_ENDPT_EPHSHK_MASK; - break; - case TUSB_XFER_ISOCHRONOUS: - flags |= USB_ENDPT_EPCTLDIS_MASK | USB_ENDPT_RETRYDIS_MASK; - break; - default: - flags |= USB_ENDPT_EPHSHK_MASK | USB_ENDPT_EPCTLDIS_MASK | USB_ENDPT_RETRYDIS_MASK; - break; - } - // TU_LOG1(" resume pipenum %d flags %x\r\n", pipenum, flags); - - KHCI->ENDPOINT[0].ENDPT = flags; - KHCI->ADDR = (KHCI->ADDR & USB_ADDR_LSEN_MASK) | pipe->dev_addr; - - unsigned const token = tu_edpt_number(pipe->ep_addr) | - ((tu_edpt_dir(pipe->ep_addr) ? TOK_PID_IN: TOK_PID_OUT) << USB_TOKEN_TOKENPID_SHIFT); - do { - while (KHCI->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ; - KHCI->TOKEN = token; - } while (--num_tokens); - if (ie) NVIC_EnableIRQ(USB0_IRQn); - return true; -} - -static void suspend_transfer(int pipenum, buffer_descriptor_t *bd) -{ - pipe_state_t *pipe = &_hcd.pipe[pipenum]; - pipe->buffer = bd->addr; - pipe->data = bd->data ^ 1; - if ((TUSB_XFER_INTERRUPT == pipe->xfer) || - (TUSB_XFER_BULK == pipe->xfer)) { - _hcd.pending |= TU_BIT(pipenum); - KHCI->INTEN |= USB_ISTAT_SOFTOK_MASK; - } -} - -static void process_tokdne(uint8_t rhport) -{ - (void)rhport; - const unsigned s = KHCI->STAT; - KHCI->ISTAT = USB_ISTAT_TOKDNE_MASK; /* fetch the next token if received */ - uint8_t const dir_in = (s & USB_STAT_TX_MASK) ? TUSB_DIR_OUT: TUSB_DIR_IN; - unsigned const odd = (s & USB_STAT_ODD_MASK) ? 1 : 0; - - buffer_descriptor_t *bd = (buffer_descriptor_t *)&_hcd.bda[s]; - endpoint_state_t *ep = &_hcd.endpoint[s >> 3]; - - /* fetch status before discarded by the next steps */ - const unsigned pid = bd->tok_pid; - - /* reset values for a next transfer */ - bd->bdt_stall = 0; - bd->dts = 1; - bd->ninc = 0; - bd->keep = 0; - /* Update the odd variable to prepare for the next transfer */ - ep->odd = odd ^ 1; - - int pipenum = ep->pipenum; - int next_pipenum; - // TU_LOG1("TOKDNE %x PID %x pipe %d\r\n", s, pid, pipenum); - - xfer_result_t result; - switch (pid) { - default: - if (continue_transfer(pipenum, bd)) - return; - result = XFER_RESULT_SUCCESS; - break; - case TOK_PID_NAK: - suspend_transfer(pipenum, bd); - next_pipenum = select_next_pipenum(pipenum); - if (0 <= next_pipenum) - resume_transfer(next_pipenum); - return; - case TOK_PID_STALL: - result = XFER_RESULT_STALLED; - break; - case TOK_PID_ERR: /* mismatch toggle bit */ - case TOK_PID_BUSTO: - result = XFER_RESULT_FAILED; - break; - } - _hcd.in_progress &= ~TU_BIT(pipenum); - pipe_state_t *pipe = &_hcd.pipe[ep->pipenum]; - hcd_event_xfer_complete(pipe->dev_addr, - tu_edpt_addr(KHCI->TOKEN & USB_TOKEN_TOKENENDPT_MASK, dir_in), - pipe->length - pipe->remaining, - result, true); - next_pipenum = select_next_pipenum(pipenum); - if (0 <= next_pipenum) - resume_transfer(next_pipenum); -} - -static void process_attach(uint8_t rhport) -{ - unsigned ctl = KHCI->CTL; - if (!(ctl & USB_CTL_JSTATE_MASK)) { - /* The attached device is a low speed device. */ - KHCI->ADDR = USB_ADDR_LSEN_MASK; - KHCI->ENDPOINT[0].ENDPT = USB_ENDPT_HOSTWOHUB_MASK; - } - hcd_event_device_attach(rhport, true); -} - -static void process_bus_reset(uint8_t rhport) -{ - KHCI->ISTAT = USB_ISTAT_TOKDNE_MASK; - KHCI->USBCTRL &= ~USB_USBCTRL_SUSP_MASK; - KHCI->CTL &= ~USB_CTL_USBENSOFEN_MASK; - KHCI->ADDR = 0; - KHCI->ENDPOINT[0].ENDPT = 0; - - hcd_event_device_remove(rhport, true); - - _hcd.in_progress = 0; - _hcd.pending = 0; - buffer_descriptor_t *bd = &_hcd.bdt[0][0]; - for (unsigned i = 0; i < 2; ++i, ++bd) { - bd->head = 0; - } -} - -/*------------------------------------------------------------------*/ -/* Host API - *------------------------------------------------------------------*/ -bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { - (void) rhport; - (void) rh_init; - KHCI->USBTRC0 |= USB_USBTRC0_USBRESET_MASK; - while (KHCI->USBTRC0 & USB_USBTRC0_USBRESET_MASK); - - tu_memclr(&_hcd, sizeof(_hcd)); - KHCI->USBTRC0 |= TU_BIT(6); /* software must set this bit to 1 */ - KHCI->BDTPAGE1 = (uint8_t)((uintptr_t)_hcd.bdt >> 8); - KHCI->BDTPAGE2 = (uint8_t)((uintptr_t)_hcd.bdt >> 16); - KHCI->BDTPAGE3 = (uint8_t)((uintptr_t)_hcd.bdt >> 24); - - KHCI->USBCTRL &= ~USB_USBCTRL_SUSP_MASK; - KHCI->CTL |= USB_CTL_ODDRST_MASK; - for (unsigned i = 0; i < 16; ++i) { - KHCI->ENDPOINT[i].ENDPT = 0; - } - KHCI->CTL &= ~USB_CTL_ODDRST_MASK; - - KHCI->SOFTHLD = 74; /* for 64-byte packets */ - // KHCI->SOFTHLD = 144; /* for low speed 8-byte packets */ - KHCI->CTL = USB_CTL_HOSTMODEEN_MASK | USB_CTL_SE0_MASK; - KHCI->USBCTRL = USB_USBCTRL_PDE_MASK; - - NVIC_ClearPendingIRQ(USB0_IRQn); - KHCI->INTEN = USB_INTEN_ATTACHEN_MASK | USB_INTEN_TOKDNEEN_MASK | - USB_INTEN_USBRSTEN_MASK | USB_INTEN_ERROREN_MASK | USB_INTEN_STALLEN_MASK; - KHCI->ERREN = 0xff; - - return true; -} - -void hcd_int_enable(uint8_t rhport) -{ - (void)rhport; - NVIC_EnableIRQ(USB0_IRQn); -} - -void hcd_int_disable(uint8_t rhport) -{ - (void)rhport; - NVIC_DisableIRQ(USB0_IRQn); -} - -uint32_t hcd_frame_number(uint8_t rhport) -{ - (void)rhport; - /* The device must be reset at least once after connection - * in order to start the frame counter. */ - if (_hcd.need_reset) hcd_port_reset(rhport); - uint32_t frmnum = KHCI->FRMNUML; - frmnum |= KHCI->FRMNUMH << 8u; - return frmnum; -} - -/*--------------------------------------------------------------------+ - * Port API - *--------------------------------------------------------------------+ */ -bool hcd_port_connect_status(uint8_t rhport) -{ - (void)rhport; - if (KHCI->ISTAT & USB_ISTAT_ATTACH_MASK) - return true; - return false; -} - -void hcd_port_reset(uint8_t rhport) -{ - (void)rhport; - KHCI->CTL &= ~USB_CTL_USBENSOFEN_MASK; - KHCI->CTL |= USB_CTL_RESET_MASK; - unsigned cnt = SystemCoreClock / 100; - while (cnt--) __NOP(); - KHCI->CTL &= ~USB_CTL_RESET_MASK; - KHCI->CTL |= USB_CTL_USBENSOFEN_MASK; - _hcd.need_reset = false; -} - -void hcd_port_reset_end(uint8_t rhport) { - (void) rhport; -} - -tusb_speed_t hcd_port_speed_get(uint8_t rhport) -{ - (void)rhport; - tusb_speed_t speed = TUSB_SPEED_FULL; - const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn); - NVIC_DisableIRQ(USB0_IRQn); - if (KHCI->ADDR & USB_ADDR_LSEN_MASK) - speed = TUSB_SPEED_LOW; - if (ie) NVIC_EnableIRQ(USB0_IRQn); - return speed; -} - -void hcd_device_close(uint8_t rhport, uint8_t dev_addr) -{ - (void)rhport; - const unsigned ie = NVIC_GetEnableIRQ(USB0_IRQn); - NVIC_DisableIRQ(USB0_IRQn); - pipe_state_t *p = &_hcd.pipe[0]; - pipe_state_t *end = &_hcd.pipe[CFG_TUH_ENDPOINT_MAX * 2]; - for (;p != end; ++p) { - if (p->dev_addr == dev_addr) - tu_memclr(p, sizeof(*p)); - } - if (ie) NVIC_EnableIRQ(USB0_IRQn); -} - -//--------------------------------------------------------------------+ -// Endpoints API -//--------------------------------------------------------------------+ -bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8]) -{ - (void)rhport; - // TU_LOG1("SETUP %u\r\n", dev_addr); - TU_ASSERT(0 == (_hcd.in_progress & TU_BIT(0))); - - int pipenum = find_pipe(dev_addr, 0); - if (pipenum < 0) return false; - - pipe_state_t *pipe = &_hcd.pipe[pipenum]; - pipe[0].data = 0; - pipe[0].buffer = (uint8_t*)(uintptr_t)setup_packet; - pipe[0].length = 8; - pipe[0].remaining = 8; - pipe[1].data = 1; - - if (1 != prepare_packets(pipenum)) - return false; - - _hcd.in_progress |= TU_BIT(pipenum); - - unsigned hostwohub = KHCI->ENDPOINT[0].ENDPT & USB_ENDPT_HOSTWOHUB_MASK; - KHCI->ENDPOINT[0].ENDPT = hostwohub | - USB_ENDPT_EPHSHK_MASK | USB_ENDPT_EPRXEN_MASK | USB_ENDPT_EPTXEN_MASK; - KHCI->ADDR = (KHCI->ADDR & USB_ADDR_LSEN_MASK) | dev_addr; - while (KHCI->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ; - KHCI->TOKEN = (TOK_PID_SETUP << USB_TOKEN_TOKENPID_SHIFT); - return true; -} - -bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc) -{ - (void)rhport; - uint8_t const ep_addr = ep_desc->bEndpointAddress; - // TU_LOG1("O %u %x\r\n", dev_addr, ep_addr); - /* Find a free pipe */ - pipe_state_t *p = &_hcd.pipe[0]; - pipe_state_t *end = &_hcd.pipe[CFG_TUH_ENDPOINT_MAX * 2]; - if (dev_addr || ep_addr) { - p += 2; - for (; p < end && (p->dev_addr || p->ep_addr); ++p) ; - if (p == end) return false; - } - p->dev_addr = dev_addr; - p->ep_addr = ep_addr; - p->max_packet_size = ep_desc->wMaxPacketSize; - p->xfer = ep_desc->bmAttributes.xfer; - p->data = 0; - if (!ep_addr) { - /* Open one more pipe for Control IN transfer */ - TU_ASSERT(TUSB_XFER_CONTROL == p->xfer); - pipe_state_t *q = p + 1; - TU_ASSERT(!q->dev_addr && !q->ep_addr); - q->dev_addr = dev_addr; - q->ep_addr = tu_edpt_addr(0, TUSB_DIR_IN); - q->max_packet_size = ep_desc->wMaxPacketSize; - q->xfer = ep_desc->bmAttributes.xfer; - q->data = 1; - } - return true; -} - -bool hcd_edpt_close(uint8_t rhport, uint8_t daddr, uint8_t ep_addr) { - (void) rhport; (void) daddr; (void) ep_addr; - return false; // TODO not implemented yet -} - -/* The address of buffer must be aligned to 4 byte boundary. And it must be at least 4 bytes long. - * DMA writes data in 4 byte unit */ -bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen) -{ - (void)rhport; - // TU_LOG1("X %u %x %x %d\r\n", dev_addr, ep_addr, (uintptr_t)buffer, buflen); - - int pipenum = find_pipe(dev_addr, ep_addr); - TU_ASSERT(0 <= pipenum); - - TU_ASSERT(0 == (_hcd.in_progress & TU_BIT(pipenum))); - unsigned const ie = NVIC_GetEnableIRQ(USB0_IRQn); - NVIC_DisableIRQ(USB0_IRQn); - pipe_state_t *pipe = &_hcd.pipe[pipenum]; - pipe->buffer = buffer; - pipe->length = buflen; - pipe->remaining = buflen; - _hcd.in_progress |= TU_BIT(pipenum); - _hcd.pending |= TU_BIT(pipenum); /* Send at the next Frame */ - KHCI->INTEN |= USB_ISTAT_SOFTOK_MASK; - if (ie) NVIC_EnableIRQ(USB0_IRQn); - return true; -} - -bool hcd_edpt_abort_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { - (void) rhport; - (void) dev_addr; - (void) ep_addr; - // TODO not implemented yet - return false; -} - -bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) { - (void) rhport; - if (!tu_edpt_number(ep_addr)) return true; - int num = find_pipe(dev_addr, ep_addr); - if (num < 0) return false; - pipe_state_t *p = &_hcd.pipe[num]; - p->data = 0; /* Reset data toggle */ - return true; -} - -/*--------------------------------------------------------------------+ - * ISR - *--------------------------------------------------------------------+*/ -void hcd_int_handler(uint8_t rhport, bool in_isr) -{ - (void) in_isr; - uint32_t is = KHCI->ISTAT; - uint32_t msk = KHCI->INTEN; - - // TU_LOG1("S %lx\r\n", is); - - /* clear disabled interrupts */ - KHCI->ISTAT = (is & ~msk & ~USB_ISTAT_TOKDNE_MASK) | USB_ISTAT_SOFTOK_MASK; - is &= msk; - - if (is & USB_ISTAT_ERROR_MASK) { - unsigned err = KHCI->ERRSTAT; - if (err) { - TU_LOG1(" ERR %x\r\n", err); - KHCI->ERRSTAT = err; - } else { - KHCI->INTEN &= ~USB_ISTAT_ERROR_MASK; - } - } - - if (is & USB_ISTAT_USBRST_MASK) { - KHCI->INTEN = (msk & ~USB_INTEN_USBRSTEN_MASK) | USB_INTEN_ATTACHEN_MASK; - process_bus_reset(rhport); - return; - } - if (is & USB_ISTAT_ATTACH_MASK) { - KHCI->INTEN = (msk & ~USB_INTEN_ATTACHEN_MASK) | USB_INTEN_USBRSTEN_MASK; - _hcd.need_reset = true; - process_attach(rhport); - return; - } - if (is & USB_ISTAT_STALL_MASK) { - KHCI->ISTAT = USB_ISTAT_STALL_MASK; - } - if (is & USB_ISTAT_SOFTOK_MASK) { - msk &= ~USB_ISTAT_SOFTOK_MASK; - KHCI->INTEN = msk; - if (_hcd.pending) { - int pipenum = __builtin_ctz(_hcd.pending); - _hcd.pending = 0; - if (!(is & USB_ISTAT_TOKDNE_MASK)) - resume_transfer(pipenum); - } - } - if (is & USB_ISTAT_TOKDNE_MASK) { - process_tokdne(rhport); - } -} - -#endif -- cgit v1.3.1 From 439a60a87f4039beba5a1d202b7ff6f9d93745dc Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 10 Jul 2026 00:17:52 +0700 Subject: dcd_ci_fs: disarm sibling BDT on short-packet OUT completion A multi-packet OUT transfer speculatively arms both even/odd BDTs to avoid NAK. When the host ends the transfer early with a short packet, the sibling BDT was left armed (own=1), desyncing the even/odd ping-pong so the next OUT packet landed at buffer+max_packet_size instead of buffer and the stack read stale data. Disarm the sibling on completion. Fixes device/mtp on Kinetis (GetDeviceInfo command was received into the wrong buffer half -> hang). Pre-existing (MSC only arms single-packet command receives so it never hit the double-buffer path). HIL: frdm_kl25z & frdm_k64f device 13/13. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01ExGPLP5eU43LR7o6yYLpNi --- src/portable/chipidea/ci_fs/dcd_ci_fs.c | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'src') diff --git a/src/portable/chipidea/ci_fs/dcd_ci_fs.c b/src/portable/chipidea/ci_fs/dcd_ci_fs.c index 0f3675349..b03670551 100644 --- a/src/portable/chipidea/ci_fs/dcd_ci_fs.c +++ b/src/portable/chipidea/ci_fs/dcd_ci_fs.c @@ -175,6 +175,17 @@ static void process_tokdne(uint8_t rhport) return; } const unsigned length = ep->length; + + /* Transfer is complete. For OUT, a multi-packet transfer speculatively arms the + * sibling (even/odd) BDT to avoid NAK. When the transfer ends early - e.g. the host + * sends a short packet before filling both buffers - that sibling is left armed + * (own=1). A leftover armed BDT desyncs the even/odd ping-pong so the next OUT + * packet lands in the wrong buffer half (buffer + max_packet_size instead of + * buffer), making the stack read stale data. Disarm it here. */ + if (dir == TUSB_DIR_OUT) { + _dcd.bdt[epnum][dir][odd ^ 1].own = 0; + } + dcd_event_xfer_complete(rhport, tu_edpt_addr(epnum, dir), length - remaining, XFER_RESULT_SUCCESS, true); -- cgit v1.3.1 From 7d7444bd8924fce9e60364893265bd2451e115e7 Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 10 Jul 2026 09:15:22 +0700 Subject: fix(ci_fs host): release stale sibling BDT on multi-packet completion hcd_ci_fs shares a single BDT set across all pipes. prepare_packets() speculatively arms the sibling (odd^1) BDT of a multi-packet transfer so it can ping-pong without NAKs. When such a transfer ends early (a short IN packet) or fails, the still-owned sibling was never released, permanently blocking the shared BDT for every other pipe. This deadlocked a 2nd device enumerating behind a hub while another device issued descriptor reads (host/device_info with CDC+MSC): the MSC's control transfers could never acquire the BDT, so it never got Set Address. Release the sibling in process_tokdne()'s completion path, but ONLY for a multi-packet transfer (length > max_packet_size): a single-packet transfer never arms a sibling, so that BDT slot may legitimately belong to another pipe's in-flight transfer and must not be disturbed (doing so unconditionally corrupts concurrent transfers, e.g. the CDC bulk-IN vs MSC enum in host/cdc_msc_hid). Mirrors the equivalent device-side fix in dcd_ci_fs.c; the host needs the multi-packet guard because its BDT set is shared across pipes. Verified on frdm_k64f (HIL): host/device_info now enumerates both CDC+MSC behind a hub, host/cdc_msc_hid still mounts the MSC (no regression). Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01ExGPLP5eU43LR7o6yYLpNi --- src/portable/chipidea/ci_fs/hcd_ci_fs.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'src') diff --git a/src/portable/chipidea/ci_fs/hcd_ci_fs.c b/src/portable/chipidea/ci_fs/hcd_ci_fs.c index 44a68a8d6..5c5d81521 100644 --- a/src/portable/chipidea/ci_fs/hcd_ci_fs.c +++ b/src/portable/chipidea/ci_fs/hcd_ci_fs.c @@ -331,6 +331,16 @@ static void process_tokdne(uint8_t rhport) } _hcd.in_progress &= ~TU_BIT(pipenum); pipe_state_t *pipe = &_hcd.pipe[ep->pipenum]; + /* A multi-packet transfer speculatively arms the sibling (odd^1) BDT (see + * prepare_packets) to ping-pong without NAKs. When it ends early (a short IN packet) + * or fails, that sibling is still owned by the SIE; since the host shares a single + * BDT set across all pipes, a leftover armed sibling blocks every other pipe forever + * (e.g. a 2nd device stuck enumerating behind a hub). Release it - but ONLY for a + * multi-packet transfer: a single-packet transfer never armed a sibling, so that + * BDT slot may legitimately belong to another pipe's in-flight transfer. */ + if (pipe->length > pipe->max_packet_size) { + ((buffer_descriptor_t *)&_hcd.bda[s ^ USB_STAT_ODD_MASK])->own = 0; + } hcd_event_xfer_complete(pipe->dev_addr, tu_edpt_addr(CI_REG->TOKEN & USB_TOKEN_TOKENENDPT_MASK, dir_in), pipe->length - pipe->remaining, -- cgit v1.3.1 From cb224400931b7fbc3477a87a258c0602092abe6b Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 17 Jul 2026 17:58:25 +0700 Subject: dcd_lpc17_40: address review findings in the iso paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From a second max-effort review of the branch: - Drop the dead TUSB_XFER_ISOCHRONOUS case in dcd_edpt_open: iso endpoints are armed via dcd_edpt_iso_alloc/activate (TUP_DCD_EDPT_ISO_ALLOC is defined for this IP), never through dcd_edpt_open, so the case and its dd->isochronous assignment were unreachable and asserted a false invariant. Only bulk/interrupt reach the switch now. - Extend the iso compile gate to the classes that actually arm an iso endpoint: DCD_ISO_ENABLED now includes CFG_TUD_BTH (bth_device.c opens an iso voice endpoint). Without it a BTH build would compile the iso machinery out and fail SET_INTERFACE at runtime. - Un-skip LPC175X_6X in the usbtest example: it shares dcd_lpc17_40.c with LPC40XX verbatim, so the "DCD has no isochronous support" skip reason no longer holds. Build-verified for lpcxpresso1769 (previously blocked by the skip). - TU_ATTR_UNUSED on the ep_id_is_iso helper: every caller is under #if DCD_ISO_ENABLED, so non-iso builds don't reference it and clang's -Wunused-function (fatal in CI) rejected the build — gcc stays quiet. Verified with the full lpc17 and lpc40 example sets under arm-clang. A fifth finding — bounding control_ep_read's PACKET_READY spin with a timeout — was implemented and REVERTED: a naive 100k-iteration bound fires on legitimately-slow control reads and intermittently drops the device (hardware-proven by interleaved A/B testing against the pre-fix binary). The infinite wait is retained; the read is only reached once out_received/ out_queued signal data is present, so the theoretical IRQ-off hang is not reachable in practice. Re-verified on ea4088_quickstart: usbtest 30/30 (repeated) + HIL 14/14. --- examples/device/usbtest/skip.txt | 1 - src/portable/nxp/lpc17_40/dcd_lpc17_40.c | 24 +++++++++++------------- 2 files changed, 11 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/examples/device/usbtest/skip.txt b/examples/device/usbtest/skip.txt index e789c4b91..792404fe4 100644 --- a/examples/device/usbtest/skip.txt +++ b/examples/device/usbtest/skip.txt @@ -4,7 +4,6 @@ mcu:SAMD11 # DCD has no isochronous support (dcd_edpt_iso_alloc refuses), tier-4 cannot enumerate: mcu:CXD56 mcu:FT90X -mcu:LPC175X_6X mcu:NUC100 mcu:NUC120 mcu:NUC505 diff --git a/src/portable/nxp/lpc17_40/dcd_lpc17_40.c b/src/portable/nxp/lpc17_40/dcd_lpc17_40.c index 6dc2b017c..b577d0e9f 100644 --- a/src/portable/nxp/lpc17_40/dcd_lpc17_40.c +++ b/src/portable/nxp/lpc17_40/dcd_lpc17_40.c @@ -20,8 +20,10 @@ #define DCD_ENDPOINT_MAX 32 // The iso machinery (5th DD word + packet-size memory) costs USB RAM on every build; -// compile it only when a class that can open an iso endpoint is enabled. -#define DCD_ISO_ENABLED (CFG_TUD_AUDIO || CFG_TUD_VIDEO || CFG_TUD_VENDOR) +// compile it only when a class that can open an iso endpoint is enabled. Keep this in +// sync with the classes that actually arm an iso endpoint: audio, video, BTH (voice), +// and vendor (its optional CFG_TUD_VENDOR_EP_ISO_* endpoints, exercised by usbtest). +#define DCD_ISO_ENABLED (CFG_TUD_AUDIO || CFG_TUD_VIDEO || CFG_TUD_VENDOR || CFG_TUD_BTH) typedef struct TU_ATTR_ALIGNED(4) { @@ -64,7 +66,9 @@ TU_VERIFY_STATIC( sizeof(dma_desc_t) == (DCD_ISO_ENABLED ? 20 : 16), "size is no // Hardware fixes endpoint type by number: 3, 6, 9, 12 are the iso-capable ones. // Constant per ep_id (= 2*epnum + dir) — unlike dd->isochronous, which dcd_edpt_xfer // transiently zeroes while rebuilding the DD, this is safe to dispatch on from the ISR. -TU_ATTR_ALWAYS_INLINE static inline bool ep_id_is_iso(uint8_t ep_id) { +// TU_ATTR_UNUSED: every caller is under #if DCD_ISO_ENABLED, so non-iso builds don't +// reference it and clang -Wunused-function (fatal) would otherwise reject the build. +TU_ATTR_UNUSED TU_ATTR_ALWAYS_INLINE static inline bool ep_id_is_iso(uint8_t ep_id) { uint8_t const epnum = (uint8_t)(ep_id >> 1); return (epnum % 3) == 0 && (epnum != 0) && (epnum != 15); } @@ -360,8 +364,9 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) uint8_t const epnum = tu_edpt_number(p_endpoint_desc->bEndpointAddress); uint8_t const ep_id = ep_addr2idx(p_endpoint_desc->bEndpointAddress); - // Endpoint type is fixed to endpoint number - // 1: interrupt, 2: Bulk, 3: Iso and so on + // Endpoint type is fixed to endpoint number (1 interrupt, 2 bulk, 3 iso, ...). + // Iso endpoints are armed via dcd_edpt_iso_alloc/activate, never through here + // (TUP_DCD_EDPT_ISO_ALLOC is defined for this IP), so only bulk/interrupt land here. switch ( p_endpoint_desc->bmAttributes.xfer ) { case TUSB_XFER_INTERRUPT: @@ -372,11 +377,6 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) TU_ASSERT((epnum % 3) == 2 || (epnum == 15)); break; - case TUSB_XFER_ISOCHRONOUS: - // iso machinery is compiled out when no iso-capable class is enabled - TU_ASSERT(DCD_ISO_ENABLED && (epnum % 3) == 0 && (epnum != 0) && (epnum != 15)); - break; - default: break; } @@ -387,9 +387,7 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) //------------- first DD prepare -------------// dma_desc_t* const dd = &_dcd.dd[ep_id]; - tu_memclr(dd, sizeof(dma_desc_t)); - - dd->isochronous = (p_endpoint_desc->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS) ? 1 : 0; + tu_memclr(dd, sizeof(dma_desc_t)); // non-iso: isochronous stays 0 dd->max_packet_size = ep_size; dd->retired = 1; // invalid at first -- cgit v1.3.1 From 4782770e7f5f4a726f9739d940c110196807f15c Mon Sep 17 00:00:00 2001 From: hathach Date: Sat, 18 Jul 2026 00:18:27 +0700 Subject: fix(ci_fs): address code-review findings in host/device drivers Host (hcd_ci_fs.c): - Release the speculatively-armed sibling BDT on the NAK path (IN only) as well as on completion, so a NAKed multi-packet IN no longer leaks a BDT that stays own=1 and blocks every same-direction pipe. Both paths now go through a single release_sibling_bd() helper (was a copy-pasted disarm). - Clear the ENTIRE shared BDT (both directions) on bus reset; clearing only the IN half left a stale OUT/SETUP descriptor after a disconnect mid-OUT, blocking the first control transfer on re-enumeration. - Size bda[] to span the whole BDT (2*2*4) so STAT-indexed access is within the declared array bounds (was out-of-declared-bounds, benign via union). Shared (ci_fs_type.h): - Hoist buffer_descriptor_t and the TOK_PID enum out of the device and host drivers into the shared header so the identical definitions cannot drift. Board (kinetis_k): - Drop a redundant local in board_get_unique_id. Build-verified: host + kinetis k/kl/k32l + MCX. HIL: frdm_k64f host 2/2 (cdc_msc_hid + device_info); frdm_kl25z device core suite green with the relocated definitions. --- hw/bsp/kinetis_k/family.c | 3 +- src/portable/chipidea/ci_fs/ci_fs_type.h | 54 ++++++++++++++++++++ src/portable/chipidea/ci_fs/dcd_ci_fs.c | 38 +------------- src/portable/chipidea/ci_fs/hcd_ci_fs.c | 88 ++++++++++++-------------------- 4 files changed, 88 insertions(+), 95 deletions(-) (limited to 'src') diff --git a/hw/bsp/kinetis_k/family.c b/hw/bsp/kinetis_k/family.c index 238123f30..2df3313b8 100644 --- a/hw/bsp/kinetis_k/family.c +++ b/hw/bsp/kinetis_k/family.c @@ -163,14 +163,13 @@ size_t board_get_unique_id(uint8_t id[], size_t max_len) { (void) max_len; // Kinetis 128-bit Unique Identification Register (SIM->UIDH/UIDMH/UIDML/UIDL) uint32_t* id32 = (uint32_t*) (uintptr_t) id; - uint8_t const len = 16; id32[0] = SIM->UIDH; id32[1] = SIM->UIDMH; id32[2] = SIM->UIDML; id32[3] = SIM->UIDL; - return len; + return 16; } #if CFG_TUSB_OS == OPT_OS_NONE diff --git a/src/portable/chipidea/ci_fs/ci_fs_type.h b/src/portable/chipidea/ci_fs/ci_fs_type.h index a525c96ca..857a75253 100644 --- a/src/portable/chipidea/ci_fs/ci_fs_type.h +++ b/src/portable/chipidea/ci_fs/ci_fs_type.h @@ -27,6 +27,60 @@ extern "C" { // align 4 is used to get rid of reserved fields #define _va32 volatile TU_ATTR_ALIGNED(4) +//--------------------------------------------------------------------+ +// Buffer Descriptor Table (BDT) - shared by the device (dcd) and host (hcd) drivers +// since both target the same ChipIdea-FS silicon. Keep the layout in one place so a +// fix cannot silently drift between the two drivers. +//--------------------------------------------------------------------+ + +// Token PID values reported in the BDT tok_pid field / written to the TOKEN register. +// The device driver only uses OUT/IN/SETUP; the rest are host-only. +enum { + TOK_PID_OUT = 0x1u, + TOK_PID_IN = 0x9u, + TOK_PID_SETUP = 0xDu, + TOK_PID_DATA0 = 0x3u, + TOK_PID_DATA1 = 0xbu, + TOK_PID_ACK = 0x2u, + TOK_PID_STALL = 0xeu, + TOK_PID_NAK = 0xau, + TOK_PID_BUSTO = 0x0u, + TOK_PID_ERR = 0xfu, +}; + +// Note: this header is included before the CMSIS device header, so use plain `volatile` +// rather than CMSIS `__IO`. +typedef struct TU_ATTR_PACKED +{ + union { + uint32_t head; + struct { + union { + struct { + uint16_t : 2; + volatile uint16_t tok_pid : 4; + uint16_t data : 1; + volatile uint16_t own : 1; + uint16_t : 8; + }; + struct { + uint16_t : 2; + uint16_t bdt_stall : 1; + uint16_t dts : 1; + uint16_t ninc : 1; + uint16_t keep : 1; + uint16_t : 10; + }; + }; + volatile uint16_t bc : 10; + uint16_t : 6; + }; + }; + uint8_t *addr; +}buffer_descriptor_t; + +TU_VERIFY_STATIC( sizeof(buffer_descriptor_t) == 8, "size is not correct" ); + typedef struct { _va32 uint8_t PER_ID; // [00] Peripheral ID register _va32 uint8_t ID_COMP; // [04] Peripheral ID complement register diff --git a/src/portable/chipidea/ci_fs/dcd_ci_fs.c b/src/portable/chipidea/ci_fs/dcd_ci_fs.c index b03670551..295f2e578 100644 --- a/src/portable/chipidea/ci_fs/dcd_ci_fs.c +++ b/src/portable/chipidea/ci_fs/dcd_ci_fs.c @@ -24,43 +24,7 @@ //--------------------------------------------------------------------+ // MACRO TYPEDEF CONSTANT ENUM DECLARATION //--------------------------------------------------------------------+ - -enum { - TOK_PID_OUT = 0x1u, - TOK_PID_IN = 0x9u, - TOK_PID_SETUP = 0xDu, -}; - -typedef struct TU_ATTR_PACKED -{ - union { - uint32_t head; - struct { - union { - struct { - uint16_t : 2; - __IO uint16_t tok_pid : 4; - uint16_t data : 1; - __IO uint16_t own : 1; - uint16_t : 8; - }; - struct { - uint16_t : 2; - uint16_t bdt_stall : 1; - uint16_t dts : 1; - uint16_t ninc : 1; - uint16_t keep : 1; - uint16_t : 10; - }; - }; - __IO uint16_t bc : 10; - uint16_t : 6; - }; - }; - uint8_t *addr; -}buffer_descriptor_t; - -TU_VERIFY_STATIC( sizeof(buffer_descriptor_t) == 8, "size is not correct" ); +// TOK_PID_* and buffer_descriptor_t are shared with the host driver in ci_fs_type.h typedef struct TU_ATTR_PACKED { diff --git a/src/portable/chipidea/ci_fs/hcd_ci_fs.c b/src/portable/chipidea/ci_fs/hcd_ci_fs.c index 5c5d81521..a6f5405b5 100644 --- a/src/portable/chipidea/ci_fs/hcd_ci_fs.c +++ b/src/portable/chipidea/ci_fs/hcd_ci_fs.c @@ -31,50 +31,7 @@ //--------------------------------------------------------------------+ // MACRO TYPEDEF CONSTANT ENUM DECLARATION //--------------------------------------------------------------------+ - -enum { - TOK_PID_OUT = 0x1u, - TOK_PID_IN = 0x9u, - TOK_PID_SETUP = 0xDu, - TOK_PID_DATA0 = 0x3u, - TOK_PID_DATA1 = 0xbu, - TOK_PID_ACK = 0x2u, - TOK_PID_STALL = 0xeu, - TOK_PID_NAK = 0xau, - TOK_PID_BUSTO = 0x0u, - TOK_PID_ERR = 0xfu, -}; - -typedef struct TU_ATTR_PACKED -{ - union { - uint32_t head; - struct { - union { - struct { - uint16_t : 2; - __IO uint16_t tok_pid : 4; - uint16_t data : 1; - __IO uint16_t own : 1; - uint16_t : 8; - }; - struct { - uint16_t : 2; - uint16_t bdt_stall : 1; - uint16_t dts : 1; - uint16_t ninc : 1; - uint16_t keep : 1; - uint16_t : 10; - }; - }; - __IO uint16_t bc : 10; - uint16_t : 6; - }; - }; - uint8_t *addr; -}buffer_descriptor_t; - -TU_VERIFY_STATIC( sizeof(buffer_descriptor_t) == 8, "size is not correct" ); +// TOK_PID_* and buffer_descriptor_t are shared with the device driver in ci_fs_type.h typedef struct TU_ATTR_PACKED { @@ -115,7 +72,10 @@ typedef struct union { /* [OUT,IN][EVEN,ODD] */ buffer_descriptor_t bdt[2][2]; - uint16_t bda[2*2]; + /* bda aliases bdt for STAT-register indexing: STAT gives the byte-offset/2 of the + * completed BD, so it indexes bda[] in uint16_t units. Each buffer_descriptor_t is + * 4 uint16_t, hence 2*2*4 elements to span the whole table (must equal sizeof bdt). */ + uint16_t bda[2*2*4]; }; endpoint_state_t endpoint[2]; pipe_state_t pipe[CFG_TUH_ENDPOINT_MAX * 2]; @@ -282,6 +242,22 @@ static void suspend_transfer(int pipenum, buffer_descriptor_t *bd) } } +// Release the speculatively-armed sibling BDT of a multi-packet transfer. +// prepare_packets arms the sibling (odd^1) BDT (own=1) so a multi-packet transfer can +// ping-pong without NAKs. When the transfer ends - completes early on a short IN packet, +// stalls/errors, or (for IN) is NAKed before the sibling's token is issued - that sibling +// is left owned by the SIE. Because the host shares ONE BDT set across all pipes, a +// leftover armed sibling blocks every other pipe forever (e.g. a 2nd device stuck +// enumerating behind a hub). Release it - but ONLY for a multi-packet transfer: a +// single-packet transfer never armed a sibling, so that BDT slot may legitimately belong +// to another pipe's in-flight transfer. +static inline void release_sibling_bd(unsigned s, const pipe_state_t *pipe) +{ + if (pipe->length > pipe->max_packet_size) { + ((buffer_descriptor_t *)&_hcd.bda[s ^ USB_STAT_ODD_MASK])->own = 0; + } +} + static void process_tokdne(uint8_t rhport) { (void)rhport; @@ -316,6 +292,12 @@ static void process_tokdne(uint8_t rhport) result = XFER_RESULT_SUCCESS; break; case TOK_PID_NAK: + // Release the speculatively-armed sibling so the deferred retry (and any other pipe + // sharing the single BDT) can claim it; otherwise it stays own=1 forever and every + // same-direction transfer wedges. IN only: an IN issues just one token so the sibling + // was never put on the wire, whereas an OUT issues both tokens and its sibling may + // still be in flight - touching it there would race the SIE write-back. + if (TUSB_DIR_IN == dir_in) release_sibling_bd(s, &_hcd.pipe[pipenum]); suspend_transfer(pipenum, bd); next_pipenum = select_next_pipenum(pipenum); if (0 <= next_pipenum) @@ -331,16 +313,7 @@ static void process_tokdne(uint8_t rhport) } _hcd.in_progress &= ~TU_BIT(pipenum); pipe_state_t *pipe = &_hcd.pipe[ep->pipenum]; - /* A multi-packet transfer speculatively arms the sibling (odd^1) BDT (see - * prepare_packets) to ping-pong without NAKs. When it ends early (a short IN packet) - * or fails, that sibling is still owned by the SIE; since the host shares a single - * BDT set across all pipes, a leftover armed sibling blocks every other pipe forever - * (e.g. a 2nd device stuck enumerating behind a hub). Release it - but ONLY for a - * multi-packet transfer: a single-packet transfer never armed a sibling, so that - * BDT slot may legitimately belong to another pipe's in-flight transfer. */ - if (pipe->length > pipe->max_packet_size) { - ((buffer_descriptor_t *)&_hcd.bda[s ^ USB_STAT_ODD_MASK])->own = 0; - } + release_sibling_bd(s, pipe); hcd_event_xfer_complete(pipe->dev_addr, tu_edpt_addr(CI_REG->TOKEN & USB_TOKEN_TOKENENDPT_MASK, dir_in), pipe->length - pipe->remaining, @@ -373,8 +346,11 @@ static void process_bus_reset(uint8_t rhport) _hcd.in_progress = 0; _hcd.pending = 0; + // Clear the ENTIRE shared BDT (both directions, both even/odd). Clearing only the IN + // pair left a stale OUT/SETUP descriptor (own=1) after a disconnect mid-OUT, which then + // blocks the first control transfer on re-enumeration. buffer_descriptor_t *bd = &_hcd.bdt[0][0]; - for (unsigned i = 0; i < 2; ++i, ++bd) { + for (unsigned i = 0; i < 2 * 2; ++i, ++bd) { bd->head = 0; } } -- cgit v1.3.1 From 127dd2ca26dcba18d14e745bec4c1a69b55a2c01 Mon Sep 17 00:00:00 2001 From: Jie Feng Date: Wed, 10 Jun 2026 18:25:08 +0800 Subject: add py32f0 support --- .gitignore | 1 + .../py32f0/boards/py32f071_dev_board/board.cmake | 9 ++ hw/bsp/py32f0/boards/py32f071_dev_board/board.h | 75 ++++++++++++ hw/bsp/py32f0/boards/py32f071_dev_board/board.mk | 8 ++ .../boards/py32f071_dev_board/py32f071_hal_conf.h | 105 ++++++++++++++++ .../py32f0/boards/py32f071_dev_board/py32f071xb.ld | 122 +++++++++++++++++++ hw/bsp/py32f0/family.c | 133 +++++++++++++++++++++ hw/bsp/py32f0/family.cmake | 97 +++++++++++++++ hw/bsp/py32f0/family.mk | 57 +++++++++ src/common/tusb_mcu.h | 11 ++ src/portable/mentor/musb/dcd_musb.c | 51 ++++++-- src/portable/mentor/musb/musb_max32.h | 1 + src/portable/mentor/musb/musb_py32.h | 79 ++++++++++++ src/portable/mentor/musb/musb_ti.h | 1 + src/portable/mentor/musb/musb_type.h | 71 +++++++++++ src/tusb_option.h | 13 +- tools/get_deps.py | 6 + 17 files changed, 829 insertions(+), 11 deletions(-) create mode 100644 hw/bsp/py32f0/boards/py32f071_dev_board/board.cmake create mode 100644 hw/bsp/py32f0/boards/py32f071_dev_board/board.h create mode 100644 hw/bsp/py32f0/boards/py32f071_dev_board/board.mk create mode 100644 hw/bsp/py32f0/boards/py32f071_dev_board/py32f071_hal_conf.h create mode 100644 hw/bsp/py32f0/boards/py32f071_dev_board/py32f071xb.ld create mode 100644 hw/bsp/py32f0/family.c create mode 100644 hw/bsp/py32f0/family.cmake create mode 100644 hw/bsp/py32f0/family.mk create mode 100644 src/portable/mentor/musb/musb_py32.h (limited to 'src') diff --git a/.gitignore b/.gitignore index 145069e72..ca745ee19 100644 --- a/.gitignore +++ b/.gitignore @@ -81,6 +81,7 @@ hw/mcu/gd/ hw/mcu/hpmicro/ hw/mcu/infineon/ hw/mcu/microchip/ +hw/mcu/puya/ hw/mcu/mindmotion/ hw/mcu/nordic/nrfx/ hw/mcu/nuvoton/ diff --git a/hw/bsp/py32f0/boards/py32f071_dev_board/board.cmake b/hw/bsp/py32f0/boards/py32f071_dev_board/board.cmake new file mode 100644 index 000000000..6c9c58e3d --- /dev/null +++ b/hw/bsp/py32f0/boards/py32f071_dev_board/board.cmake @@ -0,0 +1,9 @@ +set(PYOCD_TARGET py32f071ex8) +set(LD_FILE_GNU ${CMAKE_CURRENT_LIST_DIR}/py32f071xb.ld) + +function(update_board TARGET) + target_compile_definitions(${TARGET} PUBLIC + PY32F071xB + CFG_EXAMPLE_VIDEO_READONLY + ) +endfunction() diff --git a/hw/bsp/py32f0/boards/py32f071_dev_board/board.h b/hw/bsp/py32f0/boards/py32f071_dev_board/board.h new file mode 100644 index 000000000..ddd67bb6a --- /dev/null +++ b/hw/bsp/py32f0/boards/py32f071_dev_board/board.h @@ -0,0 +1,75 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026, Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef BOARD_H_ +#define BOARD_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +#define LED_PORT GPIOB +#define LED_PIN GPIO_PIN_2 +#define LED_STATE_ON 0 + +#define BUTTON_PORT GPIOB +#define BUTTON_PIN GPIO_PIN_0 +#define BUTTON_STATE_ACTIVE 0 + +static inline void board_py32f0_clock_init(void) { + RCC_OscInitTypeDef RCC_OscInitStruct = {0}; + RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; + + RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE | RCC_OSCILLATORTYPE_HSI | + RCC_OSCILLATORTYPE_LSI | RCC_OSCILLATORTYPE_LSE; + RCC_OscInitStruct.HSIState = RCC_HSI_ON; + RCC_OscInitStruct.HSIDiv = RCC_HSI_DIV1; + RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_16MHz; + RCC_OscInitStruct.HSEState = RCC_HSE_ON; + RCC_OscInitStruct.HSEFreq = RCC_HSE_16_32MHz; + RCC_OscInitStruct.LSIState = RCC_LSI_OFF; + RCC_OscInitStruct.LSEState = RCC_LSE_OFF; + RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; + RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; + RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL2; + if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { + while (1) {} + } + + RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1; + RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; + RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; + RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; + if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK) { + while (1) {} + } +} + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/hw/bsp/py32f0/boards/py32f071_dev_board/board.mk b/hw/bsp/py32f0/boards/py32f071_dev_board/board.mk new file mode 100644 index 000000000..61a2c578c --- /dev/null +++ b/hw/bsp/py32f0/boards/py32f071_dev_board/board.mk @@ -0,0 +1,8 @@ +CFLAGS += \ + -DPY32F071xB \ + -DCFG_EXAMPLE_VIDEO_READONLY + +LD_FILE = $(BOARD_PATH)/py32f071xb.ld +PYOCD_TARGET = py32f071ex8 + +flash: flash-pyocd diff --git a/hw/bsp/py32f0/boards/py32f071_dev_board/py32f071_hal_conf.h b/hw/bsp/py32f0/boards/py32f071_dev_board/py32f071_hal_conf.h new file mode 100644 index 000000000..e513bf8cb --- /dev/null +++ b/hw/bsp/py32f0/boards/py32f071_dev_board/py32f071_hal_conf.h @@ -0,0 +1,105 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026, Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef PY32F071_HAL_CONF_H_ +#define PY32F071_HAL_CONF_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +#define HAL_MODULE_ENABLED +#define HAL_RCC_MODULE_ENABLED +#define HAL_FLASH_MODULE_ENABLED +#define HAL_GPIO_MODULE_ENABLED +#define HAL_PWR_MODULE_ENABLED +#define HAL_CORTEX_MODULE_ENABLED + +#if !defined(HSI_VALUE) + #define HSI_VALUE ((uint32_t) 8000000) +#endif + +#if !defined(HSE_VALUE) + #define HSE_VALUE ((uint32_t) 24000000) +#endif + +#if !defined(HSE_STARTUP_TIMEOUT) + #define HSE_STARTUP_TIMEOUT ((uint32_t) 200) +#endif + +#if !defined(LSI_VALUE) + #define LSI_VALUE ((uint32_t) 32768) +#endif + +#if !defined(LSE_VALUE) + #define LSE_VALUE ((uint32_t) 32768) +#endif + +#if !defined(LSE_STARTUP_TIMEOUT) + #define LSE_STARTUP_TIMEOUT ((uint32_t) 5000) +#endif + +#define VDD_VALUE ((uint32_t) 3300) +#define TICK_INT_PRIORITY ((uint32_t) 3) +#define USE_RTOS 0 +#define PREFETCH_ENABLE 0 + +#ifdef HAL_MODULE_ENABLED + #include "py32f0xx_hal.h" +#endif + +#ifdef HAL_RCC_MODULE_ENABLED + #include "py32f071_hal_rcc.h" +#endif + +#ifdef HAL_FLASH_MODULE_ENABLED + #include "py32f071_hal_flash.h" +#endif + +#ifdef HAL_GPIO_MODULE_ENABLED + #include "py32f071_hal_gpio.h" +#endif + +#ifdef HAL_PWR_MODULE_ENABLED + #include "py32f071_hal_pwr.h" +#endif + +#ifdef HAL_CORTEX_MODULE_ENABLED + #include "py32f071_hal_cortex.h" +#endif + +#ifdef USE_FULL_ASSERT +void assert_failed(uint8_t *file, uint32_t line); + #define assert_param(expr) ((expr) ? (void) 0U : assert_failed((uint8_t *) __FILE__, __LINE__)) +#else + #define assert_param(expr) ((void) 0U) +#endif + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/hw/bsp/py32f0/boards/py32f071_dev_board/py32f071xb.ld b/hw/bsp/py32f0/boards/py32f071_dev_board/py32f071xb.ld new file mode 100644 index 000000000..bb6b88d2c --- /dev/null +++ b/hw/bsp/py32f0/boards/py32f071_dev_board/py32f071xb.ld @@ -0,0 +1,122 @@ +ENTRY(Reset_Handler) + +_estack = ORIGIN(RAM) + LENGTH(RAM); +_Min_Heap_Size = 0x200; +_Min_Stack_Size = 0x400; + +MEMORY +{ + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 16K + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K +} + +SECTIONS +{ + .isr_vector : + { + . = ALIGN(4); + KEEP(*(.isr_vector)) + . = ALIGN(4); + } >FLASH + + .text : + { + . = ALIGN(4); + *(.text) + *(.text*) + *(.glue_7) + *(.glue_7t) + *(.eh_frame) + KEEP(*(.init)) + KEEP(*(.fini)) + . = ALIGN(4); + _etext = .; + } >FLASH + + .rodata : + { + . = ALIGN(4); + *(.rodata) + *(.rodata*) + . = ALIGN(4); + } >FLASH + + .ARM.extab : + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } >FLASH + + .ARM : + { + __exidx_start = .; + *(.ARM.exidx*) + __exidx_end = .; + } >FLASH + + .preinit_array : + { + PROVIDE_HIDDEN(__preinit_array_start = .); + KEEP(*(.preinit_array*)) + PROVIDE_HIDDEN(__preinit_array_end = .); + } >FLASH + + .init_array : + { + PROVIDE_HIDDEN(__init_array_start = .); + KEEP(*(SORT(.init_array.*))) + KEEP(*(.init_array*)) + PROVIDE_HIDDEN(__init_array_end = .); + } >FLASH + + .fini_array : + { + PROVIDE_HIDDEN(__fini_array_start = .); + KEEP(*(SORT(.fini_array.*))) + KEEP(*(.fini_array*)) + PROVIDE_HIDDEN(__fini_array_end = .); + } >FLASH + + _sidata = LOADADDR(.data); + + .data : + { + . = ALIGN(4); + _sdata = .; + *(.data) + *(.data*) + . = ALIGN(4); + _edata = .; + } >RAM AT> FLASH + + . = ALIGN(4); + .bss : + { + _sbss = .; + __bss_start__ = _sbss; + *(.bss) + *(.bss*) + *(COMMON) + . = ALIGN(4); + _ebss = .; + __bss_end__ = _ebss; + } >RAM + + ._user_heap_stack : + { + . = ALIGN(8); + PROVIDE(end = .); + PROVIDE(_end = .); + . = . + _Min_Heap_Size; + . = . + _Min_Stack_Size; + . = ALIGN(8); + } >RAM + + /DISCARD/ : + { + libc.a(*) + libm.a(*) + libgcc.a(*) + } + + .ARM.attributes 0 : { *(.ARM.attributes) } +} diff --git a/hw/bsp/py32f0/family.c b/hw/bsp/py32f0/family.c new file mode 100644 index 000000000..1428b1fa9 --- /dev/null +++ b/hw/bsp/py32f0/family.c @@ -0,0 +1,133 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026, Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +/* metadata: + manufacturer: Puya +*/ + +#include "py32f0xx_hal.h" +#include "bsp/board_api.h" +#include "board.h" + +void USB_IRQHandler(void) { + tud_int_handler(0); +} + +void USBD_IRQHandler(void) { + tud_int_handler(0); +} + +void HAL_MspInit(void) { + __HAL_RCC_SYSCFG_CLK_ENABLE(); + __HAL_RCC_PWR_CLK_ENABLE(); +} + +void board_init(void) { + HAL_Init(); + board_py32f0_clock_init(); + + __HAL_RCC_SYSCFG_CLK_ENABLE(); + __HAL_RCC_PWR_CLK_ENABLE(); + __HAL_RCC_GPIOA_CLK_ENABLE(); + __HAL_RCC_GPIOB_CLK_ENABLE(); + +#if CFG_TUSB_OS == OPT_OS_NONE + SysTick_Config(SystemCoreClock / 1000); +#elif CFG_TUSB_OS == OPT_OS_FREERTOS + SysTick->CTRL &= ~1U; + NVIC_SetPriority(USB_IRQn, configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY); +#endif + + GPIO_InitTypeDef GPIO_InitStruct; + + GPIO_InitStruct.Pin = LED_PIN; + GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStruct.Pull = GPIO_PULLUP; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; + HAL_GPIO_Init(LED_PORT, &GPIO_InitStruct); + board_led_write(false); + + GPIO_InitStruct.Pin = BUTTON_PIN; + GPIO_InitStruct.Mode = GPIO_MODE_INPUT; + GPIO_InitStruct.Pull = GPIO_PULLUP; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; + HAL_GPIO_Init(BUTTON_PORT, &GPIO_InitStruct); + + __HAL_RCC_USB_CLK_ENABLE(); +} + +void board_led_write(bool state) { + GPIO_PinState pin_state = (GPIO_PinState) (state ? LED_STATE_ON : (1 - LED_STATE_ON)); + HAL_GPIO_WritePin(LED_PORT, LED_PIN, pin_state); +} + +uint32_t board_button_read(void) { + return BUTTON_STATE_ACTIVE == HAL_GPIO_ReadPin(BUTTON_PORT, BUTTON_PIN); +} + +size_t board_get_unique_id(uint8_t id[], size_t max_len) { + (void) max_len; + volatile uint32_t * py32_uuid = (volatile uint32_t *) UID_BASE; + uint32_t* id32 = (uint32_t*) (uintptr_t) id; + uint8_t const len = 12; + + id32[0] = py32_uuid[0]; + id32[1] = py32_uuid[1]; + id32[2] = py32_uuid[2]; + + return len; +} + +int board_uart_read(uint8_t *buf, int len) { + (void) buf; (void) len; + return 0; +} + +int board_uart_write(void const *buf, int len) { + (void) buf; (void) len; + return -1; +} + +#if CFG_TUSB_OS == OPT_OS_NONE +volatile uint32_t system_ticks = 0; + +void SysTick_Handler(void) { + HAL_IncTick(); + system_ticks++; +} + +uint32_t tusb_time_millis_api(void) { + return system_ticks; +} +#endif + +void HardFault_Handler(void) { + __asm("BKPT #0\n"); +} + +void _init(void); +void _init(void) { +} diff --git a/hw/bsp/py32f0/family.cmake b/hw/bsp/py32f0/family.cmake new file mode 100644 index 000000000..2416ace88 --- /dev/null +++ b/hw/bsp/py32f0/family.cmake @@ -0,0 +1,97 @@ +include_guard() + +include(${CMAKE_CURRENT_LIST_DIR}/boards/${BOARD}/board.cmake) + +if (NOT DEFINED PY32_SDK_NAME) + set(PY32_SDK_NAME PY32F071_Firmware) +endif () +if (NOT DEFINED PY32_SERIES) + set(PY32_SERIES PY32F071) +endif () +if (NOT DEFINED PY32_TEMPLATE) + set(PY32_TEMPLATE PY32F071xx_Templates) +endif () +if (NOT DEFINED PY32_STARTUP) + set(PY32_STARTUP startup_py32f071xx.s) +endif () +if (NOT DEFINED PY32_SYSTEM_SOURCE) + set(PY32_SYSTEM_SOURCE system_py32f071.c) +endif () +if (NOT DEFINED PY32_HAL_PREFIX) + set(PY32_HAL_PREFIX py32f071) +endif () +set(PY32_SDK ${TOP}/hw/mcu/puya/${PY32_SDK_NAME}) +set(PY32_HAL ${PY32_SDK}/Drivers/${PY32_SERIES}_HAL_Driver) +set(PY32_CMSIS ${PY32_SDK}/Drivers/CMSIS/Device/${PY32_SERIES}) + +set(CMAKE_SYSTEM_CPU cortex-m0plus CACHE INTERNAL "System Processor") +set(CMAKE_TOOLCHAIN_FILE ${TOP}/examples/build_system/cmake/toolchain/arm_${TOOLCHAIN}.cmake) + +set(FAMILY_MCUS PY32F0 CACHE INTERNAL "") + +set(STARTUP_FILE_GNU ${PY32_SDK}/Templates/${PY32_TEMPLATE}/EIDE/${PY32_STARTUP}) +set(STARTUP_FILE_Clang ${STARTUP_FILE_GNU}) +set(LD_FILE_Clang ${LD_FILE_GNU}) + +function(family_add_board BOARD_TARGET) + add_library(${BOARD_TARGET} STATIC + ${PY32_SDK}/Templates/${PY32_TEMPLATE}/Src/${PY32_SYSTEM_SOURCE} + ${PY32_HAL}/Src/${PY32_HAL_PREFIX}_hal.c + ${PY32_HAL}/Src/${PY32_HAL_PREFIX}_hal_cortex.c + ${PY32_HAL}/Src/${PY32_HAL_PREFIX}_hal_flash.c + ${PY32_HAL}/Src/${PY32_HAL_PREFIX}_hal_gpio.c + ${PY32_HAL}/Src/${PY32_HAL_PREFIX}_hal_pwr.c + ${PY32_HAL}/Src/${PY32_HAL_PREFIX}_hal_rcc.c + ${PY32_HAL}/Src/${PY32_HAL_PREFIX}_hal_rcc_ex.c + ) + target_include_directories(${BOARD_TARGET} PUBLIC + ${CMAKE_CURRENT_FUNCTION_LIST_DIR} + ${PY32_SDK}/Drivers/CMSIS/Include + ${PY32_CMSIS}/Include + ${PY32_HAL}/Inc + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/boards/${BOARD} + ) + target_compile_definitions(${BOARD_TARGET} PRIVATE + USE_HAL_DRIVER + ) + update_board(${BOARD_TARGET}) +endfunction() + +function(family_configure_example TARGET RTOS) + family_configure_common(${TARGET} ${RTOS}) + family_add_tinyusb(${TARGET} OPT_MCU_PY32F0) + + target_sources(${TARGET} PUBLIC + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/family.c + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../board.c + ${TOP}/src/portable/mentor/musb/dcd_musb.c + ${STARTUP_FILE_${CMAKE_C_COMPILER_ID}} + ) + target_include_directories(${TARGET} PUBLIC + ${CMAKE_CURRENT_FUNCTION_LIST_DIR} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../../ + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/boards/${BOARD} + ) + + if (CMAKE_C_COMPILER_ID STREQUAL "GNU") + target_link_options(${TARGET} PUBLIC + "LINKER:--script=${LD_FILE_GNU}" + -nostartfiles + --specs=nosys.specs --specs=nano.specs + ) + elseif (CMAKE_C_COMPILER_ID STREQUAL "Clang") + message(FATAL_ERROR "Clang is not supported") + elseif (CMAKE_C_COMPILER_ID STREQUAL "IAR") + message(FATAL_ERROR "IAR is not supported") + endif () + + if (CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang") + set_source_files_properties(${CMAKE_CURRENT_FUNCTION_LIST_DIR}/family.c PROPERTIES COMPILE_FLAGS "-Wno-missing-prototypes") + endif () + set_source_files_properties(${STARTUP_FILE_${CMAKE_C_COMPILER_ID}} PROPERTIES + SKIP_LINTING ON + COMPILE_OPTIONS -w) + + family_add_bin_hex(${TARGET}) + family_flash_pyocd(${TARGET}) +endfunction() diff --git a/hw/bsp/py32f0/family.mk b/hw/bsp/py32f0/family.mk new file mode 100644 index 000000000..7ddfea5f7 --- /dev/null +++ b/hw/bsp/py32f0/family.mk @@ -0,0 +1,57 @@ +UF2_FAMILY_ID = 0x0 + +include $(TOP)/$(BOARD_PATH)/board.mk + +PY32_SDK_NAME ?= PY32F071_Firmware +PY32_SERIES ?= PY32F071 +PY32_TEMPLATE ?= PY32F071xx_Templates +PY32_STARTUP ?= startup_py32f071xx.s +PY32_SYSTEM_SOURCE ?= system_py32f071.c +PY32_HAL_PREFIX ?= py32f071 + +SDK_DIR = hw/mcu/puya/$(PY32_SDK_NAME) +HAL_DIR = $(SDK_DIR)/Drivers/$(PY32_SERIES)_HAL_Driver +CMSIS_DIR = $(SDK_DIR)/Drivers/CMSIS +CMSIS_DEVICE_DIR = $(CMSIS_DIR)/Device/$(PY32_SERIES) + +DEPS_SUBMODULES += $(SDK_DIR) + +CFLAGS += \ + -DCFG_TUSB_MCU=OPT_MCU_PY32F0 \ + -DUSE_HAL_DRIVER + +# Puya HAL leaves some CMSIS-compatible callback parameters intentionally unused. +CFLAGS += -Wno-error=unused-parameter + +MCU_DIR = $(SDK_DIR) + +SRC_C += \ + src/portable/mentor/musb/dcd_musb.c \ + hw/bsp/py32f0/family.c \ + $(SDK_DIR)/Templates/$(PY32_TEMPLATE)/Src/$(PY32_SYSTEM_SOURCE) \ + $(HAL_DIR)/Src/$(PY32_HAL_PREFIX)_hal.c \ + $(HAL_DIR)/Src/$(PY32_HAL_PREFIX)_hal_cortex.c \ + $(HAL_DIR)/Src/$(PY32_HAL_PREFIX)_hal_flash.c \ + $(HAL_DIR)/Src/$(PY32_HAL_PREFIX)_hal_gpio.c \ + $(HAL_DIR)/Src/$(PY32_HAL_PREFIX)_hal_pwr.c \ + $(HAL_DIR)/Src/$(PY32_HAL_PREFIX)_hal_rcc.c \ + $(HAL_DIR)/Src/$(PY32_HAL_PREFIX)_hal_rcc_ex.c + +SRC_S += $(SDK_DIR)/Templates/$(PY32_TEMPLATE)/EIDE/$(PY32_STARTUP) + +INC += \ + $(TOP)/hw/bsp/py32f0 \ + $(TOP)/$(CMSIS_DIR)/Include \ + $(TOP)/$(CMSIS_DEVICE_DIR)/Include \ + $(TOP)/$(HAL_DIR)/Inc \ + $(TOP)/hw/bsp/py32f0/boards/$(BOARD) + +SKIP_NANOLIB = 1 + +LDFLAGS += \ + -nostdlib -nostartfiles \ + --specs=nosys.specs --specs=nano.specs \ + -Wl,--gc-sections \ + -Wl,--print-memory-usage + +CPU_CORE ?= cortex-m0plus diff --git a/src/common/tusb_mcu.h b/src/common/tusb_mcu.h index 0959ac3a9..d599f89fb 100644 --- a/src/common/tusb_mcu.h +++ b/src/common/tusb_mcu.h @@ -703,6 +703,17 @@ #define TU_ATTR_FAST_FUNC __attribute__((section(".fast"))) +//--------------------------------------------------------------------+ +// Puya +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_PY32F0) + #define TUP_USBIP_MUSB + #define TUP_USBIP_MUSB_PY32 + #define TUP_DCD_ENDPOINT_MAX 6 + // PY32 shares the buffer between IN and OUT of the same endpoint number. + // Possible to share IN/OUT if only one direction is armed at any one time + #define CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY 1 + #endif // External USB controller diff --git a/src/portable/mentor/musb/dcd_musb.c b/src/portable/mentor/musb/dcd_musb.c index 17993f23a..45c6fe7df 100644 --- a/src/portable/mentor/musb/dcd_musb.c +++ b/src/portable/mentor/musb/dcd_musb.c @@ -24,6 +24,8 @@ #include "musb_ti.h" #elif defined(TUP_USBIP_MUSB_ADI) #include "musb_max32.h" +#elif defined(TUP_USBIP_MUSB_PY32) + #include "musb_py32.h" #else #error "Unsupported MCU" #endif @@ -45,6 +47,7 @@ typedef struct { }; uint16_t length; /* the number of bytes in the buffer */ uint16_t remaining; /* the number of bytes remaining in the buffer */ + uint16_t mps; /* maximum packet size */ bool armed; /* true while a transfer is posted */ bool use_fifo; /* true: buf is tu_fifo_t*; false: buf is plain byte pointer. */ } pipe_state_t; @@ -159,6 +162,14 @@ TU_ATTR_ALWAYS_INLINE static inline pipe_state_t* pipe_get(uint8_t epnum, tusb_d return &_dcd.pipe[idx]; } +TU_ATTR_ALWAYS_INLINE static inline uint16_t musb_mps_to_maxp(uint16_t mps) { +#if defined(TUP_USBIP_MUSB_PY32) + return (uint8_t) ((mps + 7u) / 8u); +#else + return mps; +#endif +} + //-------------------------------------------------------------------- // HW FIFO Helper // Note: Index register is already set by caller @@ -223,7 +234,9 @@ TU_ATTR_ALWAYS_INLINE static inline bool hwfifo_config(musb_regs_t* musb, unsign bool double_packet) { (void) mps; - #if defined(TUP_USBIP_MUSB_ADI) + #if defined(TUP_USBIP_MUSB_PY32) + (void) musb; (void) epnum; (void) is_rx; (void) double_packet; + #elif defined(TUP_USBIP_MUSB_ADI) // AnalogDevice FIFO sizes: EP1..7 = 512 B, EP8..9 = 2048 B, EP10..11 = 4096 B. // DPB requires FIFO >= 2 * MPS. For HS bulk (MPS=512) only EP >= 8 qualifies. // Force single-buffered on EP < 8 even if the caller requested DPB. @@ -266,7 +279,7 @@ TU_ATTR_ALWAYS_INLINE static inline void hwfifo_flush(musb_regs_t* musb, unsigne // write to txfifo using pipe_state_t info static void pipe_write(musb_regs_t* musb_regs, pipe_state_t* pipe, uint8_t epnum) { musb_ep_csr_t* ep_csr = &musb_regs->indexed_csr; - const uint16_t mps = ep_csr->tx_maxp & MUSB_TXMAXP_PACKET_SIZE_M; + const uint16_t mps = pipe->mps; const uint16_t xact_len = tu_min16(mps, pipe->remaining); volatile void *hwfifo = &musb_regs->fifo[epnum]; if (xact_len) { @@ -320,7 +333,7 @@ static void process_epin_isr(uint8_t rhport, musb_regs_t *musb_regs, uint8_t epn // release the FIFO slot by clearing RXRDY. return true if short packet static bool pipe_read(musb_regs_t* musb_regs, pipe_state_t* pipe, uint8_t epnum) { musb_ep_csr_t* ep_csr = &musb_regs->indexed_csr; // index already set in process_epout_isr() - const uint16_t mps = ep_csr->rx_maxp & MUSB_RXMAXP_PACKET_SIZE_M; + const uint16_t mps = pipe->mps; const uint16_t rx_count = ep_csr->rx_count; const uint16_t xact_len = tu_min16(tu_min16(pipe->remaining, mps), rx_count); volatile void *hwfifo = &musb_regs->fifo[epnum]; @@ -632,7 +645,11 @@ static void process_bus_reset_isr(uint8_t rhport) { hwfifo_reset(musb, i, 0); hwfifo_reset(musb, i, 1); } +#if defined(TUP_USBIP_MUSB_PY32) + dcd_event_bus_reset(rhport, TUSB_SPEED_FULL, true); +#else dcd_event_bus_reset(rhport, (musb->power & MUSB_POWER_HSMODE) ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL, true); +#endif } /*------------------------------------------------------------------ @@ -641,6 +658,11 @@ static void process_bus_reset_isr(uint8_t rhport) { #if CFG_TUSB_DEBUG >= MUSB_DEBUG static void print_musb_info(musb_regs_t* musb_regs) { +#if defined(TUP_USBIP_MUSB_PY32) + (void) musb_regs; + // musb discovery fields not present + TU_LOG1("musb py32 fixed full-speed configuration\r\n"); +#else // print version, epinfo, raminfo, config_data0, fifo_size TU_LOG1("musb version = %u.%u\r\n", musb_regs->hwvers_bit.major, musb_regs->hwvers_bit.minor); TU_LOG1("Number of endpoints: %u TX, %u RX\r\n", musb_regs->epinfo_bit.tx_ep_num, musb_regs->epinfo_bit.rx_ep_num); @@ -657,6 +679,7 @@ static void print_musb_info(musb_regs_t* musb_regs) { TU_LOG1("FIFO %u Size: TX %u RX %u\r\n", i, musb_regs->indexed_csr.fifo_size_bit.tx, musb_regs->indexed_csr.fifo_size_bit.rx); } #endif +#endif } #endif @@ -716,15 +739,23 @@ void dcd_remote_wakeup(uint8_t rhport) { void dcd_connect(uint8_t rhport) { musb_regs_t* musb_regs = MUSB_REGS(rhport); +#if defined(TUP_USBIP_MUSB_PY32) + (void) musb_regs; +#else musb_regs->power |= TUD_OPT_HIGH_SPEED ? MUSB_POWER_HSENAB : 0; musb_regs->power |= MUSB_POWER_SOFTCONN; +#endif } // Disconnect by disabling internal pull-up resistor on D+/D- void dcd_disconnect(uint8_t rhport) { musb_regs_t* musb_regs = MUSB_REGS(rhport); +#if defined(TUP_USBIP_MUSB_PY32) + (void) musb_regs; +#else musb_regs->power &= ~MUSB_POWER_SOFTCONN; +#endif } void dcd_sof_enable(uint8_t rhport, bool en) @@ -750,6 +781,7 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * ep_desc) { pipe->buf = NULL; pipe->length = 0; pipe->remaining = 0; + pipe->mps = (uint16_t) mps; pipe->armed = false; musb_regs_t* musb = MUSB_REGS(rhport); @@ -757,7 +789,7 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * ep_desc) { const uint8_t is_rx = (1 - epdir); musb_ep_maxp_csr_t* maxp_csr = &ep_csr->maxp_csr[is_rx]; - maxp_csr->maxp = mps; + maxp_csr->maxp = musb_mps_to_maxp((uint16_t) mps); maxp_csr->csrh = 0; #if MUSB_CFG_SHARED_FIFO if (epdir) { @@ -768,7 +800,7 @@ bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * ep_desc) { hwfifo_flush(musb, epn, is_rx, true); TU_ASSERT(hwfifo_config(musb, epn, is_rx, mps, ep_desc->bmAttributes.xfer == TUSB_XFER_BULK)); - musb->intren_ep[is_rx] |= TU_BIT(epn); + musb->intren_ep[is_rx ^ MUSB_INTR_EP_TX_RX_SWAP] |= TU_BIT(epn); return true; } @@ -779,6 +811,8 @@ bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet musb_regs_t* musb = MUSB_REGS(rhport); musb_ep_csr_t* ep_csr = get_ep_csr(musb, epn); const uint8_t is_rx = 1 - dir_in; + pipe_state_t *pipe = pipe_get(epn, dir_in); + pipe->mps = largest_packet_size; ep_csr->maxp_csr[is_rx].csrh = 0; return hwfifo_config(musb, epn, is_rx, largest_packet_size, true); } @@ -796,6 +830,7 @@ bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const *ep_desc ) pipe->buf = NULL; pipe->length = 0; pipe->remaining = 0; + pipe->mps = (uint16_t) mps; pipe->armed = false; musb_regs_t* musb = MUSB_REGS(rhport); @@ -803,7 +838,7 @@ bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const *ep_desc ) const uint8_t is_rx = 1 - dir_in; musb_ep_maxp_csr_t* maxp_csr = &ep_csr->maxp_csr[is_rx]; - maxp_csr->maxp = mps; + maxp_csr->maxp = musb_mps_to_maxp((uint16_t) mps); maxp_csr->csrh |= MUSB_CSRH_ISO; #if MUSB_CFG_SHARED_FIFO if (dir_in) { @@ -818,7 +853,7 @@ bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const *ep_desc ) musb->fifo_size[is_rx] = hwfifo_byte2size(mps) | MUSB_FIFOSZ_DOUBLE_PACKET; #endif - musb->intren_ep[is_rx] |= TU_BIT(epn); + musb->intren_ep[is_rx ^ MUSB_INTR_EP_TX_RX_SWAP] |= TU_BIT(epn); if (ie) musb_dcd_int_enable(rhport); @@ -839,7 +874,7 @@ void dcd_edpt_close_all(uint8_t rhport) musb_ep_maxp_csr_t* maxp_csr = &ep_csr->maxp_csr[d]; hwfifo_flush(musb, i, d, true); hwfifo_reset(musb, i, d); - maxp_csr->maxp = 0; + maxp_csr->maxp = musb_mps_to_maxp(0); maxp_csr->csrh = 0; } } diff --git a/src/portable/mentor/musb/musb_max32.h b/src/portable/mentor/musb/musb_max32.h index 628454216..9cf99126e 100644 --- a/src/portable/mentor/musb/musb_max32.h +++ b/src/portable/mentor/musb/musb_max32.h @@ -28,6 +28,7 @@ extern "C" { #define MUSB_CFG_SHARED_FIFO 1 // shared FIFO for TX and RX endpoints #define MUSB_CFG_DYNAMIC_FIFO 0 // dynamic EP FIFO sizing +#define MUSB_INTR_EP_TX_RX_SWAP 0 static const uintptr_t MUSB_BASES[] = { MXC_BASE_USBHS }; diff --git a/src/portable/mentor/musb/musb_py32.h b/src/portable/mentor/musb/musb_py32.h new file mode 100644 index 000000000..68d58ccb8 --- /dev/null +++ b/src/portable/mentor/musb/musb_py32.h @@ -0,0 +1,79 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2026, Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef TUSB_MUSB_PY32_H_ +#define TUSB_MUSB_PY32_H_ + +#include "py32f0xx.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// PY32 does not expose generic MUSB shared FIFO allocation registers; this +// selects the existing TX_MODE path required for IN endpoints. +#define MUSB_CFG_SHARED_FIFO 1 +#define MUSB_CFG_DYNAMIC_FIFO 0 +#define MUSB_INTR_EP_TX_RX_SWAP 1 + +static const uintptr_t MUSB_BASES[] = { USBD_BASE }; +static const IRQn_Type musb_irqs[] = { USB_IRQn }; + +TU_ATTR_ALWAYS_INLINE static inline void musb_dcd_phy_init(uint8_t rhport) { + musb_regs_t* musb_regs = MUSB_REGS(rhport); + + musb_regs->index = 0; + musb_regs->faddr = 0; + musb_regs->intr_usben = MUSB_IE_RESET | MUSB_IE_RESUME | MUSB_IE_SUSPND; + musb_regs->intr_txen = TU_BIT(0); + musb_regs->intr_rxen = 0; +} + +TU_ATTR_ALWAYS_INLINE static inline void musb_dcd_int_enable(uint8_t rhport) { + NVIC_EnableIRQ(musb_irqs[rhport]); +} + +TU_ATTR_ALWAYS_INLINE static inline void musb_dcd_int_disable(uint8_t rhport) { + NVIC_DisableIRQ(musb_irqs[rhport]); +} + +TU_ATTR_ALWAYS_INLINE static inline void musb_dcd_int_clear(uint8_t rhport) { + NVIC_ClearPendingIRQ(musb_irqs[rhport]); +} + +TU_ATTR_ALWAYS_INLINE static inline unsigned musb_dcd_get_int_enable(uint8_t rhport) { + return NVIC_GetEnableIRQ(musb_irqs[rhport]); +} + +TU_ATTR_ALWAYS_INLINE static inline void musb_dcd_int_handler_enter(uint8_t rhport) { + (void) rhport; +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/portable/mentor/musb/musb_ti.h b/src/portable/mentor/musb/musb_ti.h index db2e237d2..1cf26b1cb 100644 --- a/src/portable/mentor/musb/musb_ti.h +++ b/src/portable/mentor/musb/musb_ti.h @@ -30,6 +30,7 @@ #define MUSB_CFG_SHARED_FIFO 0 #define MUSB_CFG_DYNAMIC_FIFO 1 #define MUSB_CFG_DYNAMIC_FIFO_SIZE 4096 +#define MUSB_INTR_EP_TX_RX_SWAP 0 static const uintptr_t MUSB_BASES[] = { USB0_BASE }; diff --git a/src/portable/mentor/musb/musb_type.h b/src/portable/mentor/musb/musb_type.h index 3c079cb25..1e8a761c0 100644 --- a/src/portable/mentor/musb/musb_type.h +++ b/src/portable/mentor/musb/musb_type.h @@ -64,6 +64,75 @@ #define __R volatile const #endif +#if defined(TUP_USBIP_MUSB_PY32) + +typedef struct TU_ATTR_PACKED { + __IO uint8_t csrh; // 0x04, 0x08: CSRH + __IO uint8_t csrl; // 0x05, 0x09: CSRL + __IO uint8_t maxp; // 0x06, 0x0A: MAXP + __I uint8_t reserved; +} musb_ep_maxp_csr_t; + +TU_VERIFY_STATIC(sizeof(musb_ep_maxp_csr_t) == 4, "size is not correct"); + +typedef struct TU_ATTR_PACKED { + __IO uint8_t csr0l; // 0x00: CSR0 + __IO uint8_t count0; // 0x01: COUNT0 + __I uint8_t reserved_0x02[2]; + + union { + struct { + __IO uint8_t tx_csrh; // 0x04: TX CSRH + __IO uint8_t tx_csrl; // 0x05: TX CSRL + __IO uint8_t tx_maxp; // 0x06: TX MAXP + __I uint8_t reserved_0x07; + + __IO uint8_t rx_csrh; // 0x08: RX CSRH + __IO uint8_t rx_csrl; // 0x09: RX CSRL + __IO uint8_t rx_maxp; // 0x0A: RX MAXP + __I uint8_t reserved_0x0b; + }; + + musb_ep_maxp_csr_t maxp_csr[2]; + }; + + __IO uint16_t rx_count; // 0x0C: RX COUNT + __I uint8_t reserved_0x0e[2]; +} musb_ep_csr_t; + +TU_VERIFY_STATIC(sizeof(musb_ep_csr_t) == 16, "size is not correct"); + +typedef struct TU_ATTR_PACKED { + __IO uint8_t faddr; // 0x00: FADDR + __IO uint8_t power; // 0x01: POWER + __I uint8_t reserved_0x02[2]; + + __IO uint8_t intr_usb; // 0x04: INTRUSB + __IO uint8_t intr_rx; // 0x05: INTRRX + __IO uint8_t intr_tx; // 0x06: INTRTX + __I uint8_t reserved_0x07; + + __IO uint8_t intr_usben; // 0x08: INTRUSBEN + union { + struct { + __IO uint8_t intr_rxen; // 0x09: INTRRXEN + __IO uint8_t intr_txen; // 0x0A: INTRTXEN + }; + + __IO uint8_t intren_ep[2]; // 0x09-0x0A: RX, TX + }; + __I uint8_t reserved_0x0b; + + __IO uint16_t frame; // 0x0C: FRAME + __IO uint8_t index; // 0x0E: INDEX + __I uint8_t reserved_0x0f; + + musb_ep_csr_t indexed_csr; // 0x10-0x1F: Indexed CSR + __IO uint32_t fifo[16]; // 0x20-0x5F: FIFO 0-15 +} musb_regs_t; + +#else + typedef struct TU_ATTR_PACKED { __IO uint16_t maxp; // 0x00, 0x04: MAXP __IO uint8_t csrl; // 0x02, 0x06: CSRL @@ -277,6 +346,8 @@ typedef struct { TU_VERIFY_STATIC(sizeof(musb_regs_t) == 0x350, "size is not correct"); +#endif + //--------------------------------------------------------------------+ // Helper //--------------------------------------------------------------------+ diff --git a/src/tusb_option.h b/src/tusb_option.h index e19ee1629..0036fbe3c 100644 --- a/src/tusb_option.h +++ b/src/tusb_option.h @@ -104,6 +104,9 @@ #define OPT_MCU_NUC120 802 #define OPT_MCU_NUC505 803 +// Puya +#define OPT_MCU_PY32F0 850 ///< Puya PY32F0 + // Espressif #define OPT_MCU_ESP32S2 900 ///< Espressif ESP32-S2 #define OPT_MCU_ESP32S3 901 ///< Espressif ESP32-S3 @@ -349,9 +352,13 @@ //------------ MUSB --------------// #if defined(TUP_USBIP_MUSB) #define CFG_TUD_EDPT_DEDICATED_HWFIFO 1 - #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE 4 // 32 bit data - #define CFG_TUSB_FIFO_HWFIFO_DATA_ODD_16BIT_ACCESS // allow odd 16bit access - #define CFG_TUSB_FIFO_HWFIFO_DATA_ODD_8BIT_ACCESS // allow odd 8bit access + #if defined(TUP_USBIP_MUSB_PY32) + #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE 1 // 8 bit data + #else + #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE 4 // 32 bit data + #define CFG_TUSB_FIFO_HWFIFO_DATA_ODD_16BIT_ACCESS // allow odd 16bit access + #define CFG_TUSB_FIFO_HWFIFO_DATA_ODD_8BIT_ACCESS // allow odd 8bit access + #endif #define CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE 0 // fixed hwfifo #endif diff --git a/tools/get_deps.py b/tools/get_deps.py index 9d745ee26..e26810cac 100755 --- a/tools/get_deps.py +++ b/tools/get_deps.py @@ -82,6 +82,12 @@ deps_optional = { 'hw/mcu/nxp/mcux-devices-rt': ['https://github.com/nxp-mcuxpresso/mcux-devices-rt', 'dba2b523c9df61f3330bd186242f8210a8e47c45', 'imxrt'], + 'hw/mcu/puya/PY32F071_Firmware': ['https://github.com/OpenPuya/PY32F071_Firmware.git', + '73e384cddce63e3019de41d9b6af17dfc96fa536', + 'py32f0'], + 'hw/mcu/puya/PY32F072_Firmware': ['https://github.com/OpenPuya/PY32F072_Firmware.git', + 'bc3a6cdbece335a27abb8b51e6fc4911f5116185', + 'py32f0'], 'hw/mcu/raspberry_pi/FreeRTOS-Kernel': ['https://github.com/raspberrypi/FreeRTOS-Kernel.git', '4f7299d6ea746b27a9dd19e87af568e34bd65b15', 'rp2040'], -- cgit v1.3.1 From 67a28ae7535100afc80366f29cee5fad01c9bc6f Mon Sep 17 00:00:00 2001 From: Jie Feng Date: Thu, 11 Jun 2026 22:31:26 +0800 Subject: cleanup --- src/portable/mentor/musb/dcd_musb.c | 26 ++++++++++++++++++-------- src/tusb_option.h | 14 ++++++-------- 2 files changed, 24 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/portable/mentor/musb/dcd_musb.c b/src/portable/mentor/musb/dcd_musb.c index 45c6fe7df..0a174065f 100644 --- a/src/portable/mentor/musb/dcd_musb.c +++ b/src/portable/mentor/musb/dcd_musb.c @@ -735,29 +735,39 @@ void dcd_remote_wakeup(uint8_t rhport) { musb_regs->power &= ~MUSB_POWER_RESUME; } +#if defined(TUP_USBIP_MUSB_PY32) + // Connect by enabling internal pull-up resistor on D+/D- void dcd_connect(uint8_t rhport) { - musb_regs_t* musb_regs = MUSB_REGS(rhport); -#if defined(TUP_USBIP_MUSB_PY32) - (void) musb_regs; + (void) rhport; +} + +// Disconnect by disabling internal pull-up resistor on D+/D- +void dcd_disconnect(uint8_t rhport) +{ + (void) rhport; +} + #else + +// Connect by enabling internal pull-up resistor on D+/D- +void dcd_connect(uint8_t rhport) +{ + musb_regs_t* musb_regs = MUSB_REGS(rhport); musb_regs->power |= TUD_OPT_HIGH_SPEED ? MUSB_POWER_HSENAB : 0; musb_regs->power |= MUSB_POWER_SOFTCONN; -#endif } // Disconnect by disabling internal pull-up resistor on D+/D- void dcd_disconnect(uint8_t rhport) { musb_regs_t* musb_regs = MUSB_REGS(rhport); -#if defined(TUP_USBIP_MUSB_PY32) - (void) musb_regs; -#else musb_regs->power &= ~MUSB_POWER_SOFTCONN; -#endif } +#endif + void dcd_sof_enable(uint8_t rhport, bool en) { (void) rhport; diff --git a/src/tusb_option.h b/src/tusb_option.h index 0036fbe3c..f2c62cc6c 100644 --- a/src/tusb_option.h +++ b/src/tusb_option.h @@ -104,9 +104,6 @@ #define OPT_MCU_NUC120 802 #define OPT_MCU_NUC505 803 -// Puya -#define OPT_MCU_PY32F0 850 ///< Puya PY32F0 - // Espressif #define OPT_MCU_ESP32S2 900 ///< Espressif ESP32-S2 #define OPT_MCU_ESP32S3 901 ///< Espressif ESP32-S3 @@ -207,6 +204,9 @@ // HPMicro #define OPT_MCU_HPM 2600 ///< HPMicro +// Puya +#define OPT_MCU_PY32F0 2700 ///< Puya PY32F0 + // Check if configured MCU is one of listed // Apply TU_MCU_IS_EQUAL with || as separator to list of input #define TU_MCU_IS_EQUAL(_m) (CFG_TUSB_MCU == (_m)) @@ -352,13 +352,11 @@ //------------ MUSB --------------// #if defined(TUP_USBIP_MUSB) #define CFG_TUD_EDPT_DEDICATED_HWFIFO 1 - #if defined(TUP_USBIP_MUSB_PY32) - #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE 1 // 8 bit data - #else - #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE 4 // 32 bit data + #define CFG_TUSB_FIFO_HWFIFO_DATA_STRIDE 4 // 32 bit data + #if !defined(TUP_USBIP_MUSB_PY32) #define CFG_TUSB_FIFO_HWFIFO_DATA_ODD_16BIT_ACCESS // allow odd 16bit access - #define CFG_TUSB_FIFO_HWFIFO_DATA_ODD_8BIT_ACCESS // allow odd 8bit access #endif + #define CFG_TUSB_FIFO_HWFIFO_DATA_ODD_8BIT_ACCESS // allow odd 8bit access #define CFG_TUSB_FIFO_HWFIFO_ADDR_STRIDE 0 // fixed hwfifo #endif -- cgit v1.3.1 From 9dfe2e02d97960505fb96df5749d7cdf2ae82f66 Mon Sep 17 00:00:00 2001 From: Jie Feng Date: Thu, 11 Jun 2026 22:48:29 +0800 Subject: add to docs --- README.rst | 2 ++ src/portable/mentor/musb/dcd_musb.c | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/README.rst b/README.rst index 205f3f544..f897ec2f5 100644 --- a/README.rst +++ b/README.rst @@ -259,6 +259,8 @@ Supported CPUs | +---------+-------------------+--------+------+-----------+------------------------+--------------------+ | | RW61x | ✅ | ✅ | ✅ | ci_hs, ehci | | +--------------+-----------------------------+--------+------+-----------+------------------------+--------------------+ +| Puya | PY32F071, PY32F072 | ✅ | ❌ | ❌ | musb | 1-dir ep | ++--------------+-----------------------------+--------+------+-----------+------------------------+--------------------+ | Raspberry Pi | RP2040, RP2350 | ✅ | ✅ | ❌ | rp2040, pio_usb | | +--------------+-----+-----------------------+--------+------+-----------+------------------------+--------------------+ | Renesas | RX | 63N, 65N, 72N | ✅ | ✅ | ❌ | rusb2 | | diff --git a/src/portable/mentor/musb/dcd_musb.c b/src/portable/mentor/musb/dcd_musb.c index 0a174065f..69b7ef0de 100644 --- a/src/portable/mentor/musb/dcd_musb.c +++ b/src/portable/mentor/musb/dcd_musb.c @@ -736,19 +736,15 @@ void dcd_remote_wakeup(uint8_t rhport) { } #if defined(TUP_USBIP_MUSB_PY32) - -// Connect by enabling internal pull-up resistor on D+/D- void dcd_connect(uint8_t rhport) { (void) rhport; } -// Disconnect by disabling internal pull-up resistor on D+/D- void dcd_disconnect(uint8_t rhport) { (void) rhport; } - #else // Connect by enabling internal pull-up resistor on D+/D- -- cgit v1.3.1 From 9418aba918d7c4107026c894e863d9ef0ec5db81 Mon Sep 17 00:00:00 2001 From: Jie Feng Date: Thu, 18 Jun 2026 17:28:49 +0800 Subject: misc fixes --- docs/reference/device_issues.rst | 11 +++++++++++ examples/device/cdc_uac2/src/tusb_config.h | 8 +++++--- examples/device/uac2_headset/src/usb_descriptors.c | 5 +++++ hw/bsp/py32f0/boards/py32f071_dev_board/board.cmake | 1 + hw/bsp/py32f0/boards/py32f071_dev_board/board.mk | 1 + src/portable/mentor/musb/dcd_musb.c | 5 ++++- 6 files changed, 27 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/docs/reference/device_issues.rst b/docs/reference/device_issues.rst index ae9cd55f1..0850409cb 100644 --- a/docs/reference/device_issues.rst +++ b/docs/reference/device_issues.rst @@ -33,3 +33,14 @@ Reference: `CH32V30X Reference Manual`_ USBFS/USBHS controller chapter Data corruption may occur on isochronous endpoints. Due to the lacking of FIFO for interrupt status registers, later completed transfer will overwrite `INT_ST` and `RX_LEN` register if previous transfer processing is not completed. Other types of transfers are not affected. + +Puya PY32F071/072 +--------------------------------- +**Severity: Very Low** + +Reference: `PY32F07x Reference Manual` USBD chapter + +The USB device controller (MUSB-like) has 5 application endpoints EP1-EP5 with fixed FIFO sizes +shared between IN and OUT of the same endpoint number: EP1 = 512 B, EP2-4 = 128 B, EP5 = 64 B. +This is much lower than the max ISO ep size of 1024 for high EP numbers. +Place large isochronous endpoints on EP1 and size descriptors accordingly. diff --git a/examples/device/cdc_uac2/src/tusb_config.h b/examples/device/cdc_uac2/src/tusb_config.h index 358ff6747..f54a9b606 100644 --- a/examples/device/cdc_uac2/src/tusb_config.h +++ b/examples/device/cdc_uac2/src/tusb_config.h @@ -109,8 +109,10 @@ extern "C" { #define CFG_TUD_AUDIO_FUNC_1_N_FORMATS 2 // Audio format type I specifications -#if defined(__RX__) -#define CFG_TUD_AUDIO_FUNC_1_MAX_SAMPLE_RATE 48000 // 16bit/48kHz is the best quality for Renesas RX +#if defined(__RX__) || (CFG_TUSB_MCU == OPT_MCU_PY32F0) +// RX : 16bit/48kHz is the best quality for Renesas RX +// PY32F0 : 48kHz/16bit keeps ISO packets <= 98 B so both directions fit the fixed EP FIFOs (EP1 512 B, EP2 128 B) +#define CFG_TUD_AUDIO_FUNC_1_MAX_SAMPLE_RATE 48000 #else #define CFG_TUD_AUDIO_FUNC_1_MAX_SAMPLE_RATE 96000 // 24bit/96kHz is the best quality for full-speed, high-speed is needed beyond this #endif @@ -123,7 +125,7 @@ extern "C" { #define CFG_TUD_AUDIO_FUNC_1_FORMAT_1_N_BYTES_PER_SAMPLE_RX 2 #define CFG_TUD_AUDIO_FUNC_1_FORMAT_1_RESOLUTION_RX 16 -#if defined(__RX__) +#if defined(__RX__) || (CFG_TUSB_MCU == OPT_MCU_PY32F0) // 8bit in 8bit slots #define CFG_TUD_AUDIO_FUNC_1_FORMAT_2_N_BYTES_PER_SAMPLE_TX 1 #define CFG_TUD_AUDIO_FUNC_1_FORMAT_2_RESOLUTION_TX 8 diff --git a/examples/device/uac2_headset/src/usb_descriptors.c b/examples/device/uac2_headset/src/usb_descriptors.c index 1615b92ec..27b6c930c 100644 --- a/examples/device/uac2_headset/src/usb_descriptors.c +++ b/examples/device/uac2_headset/src/usb_descriptors.c @@ -98,6 +98,11 @@ uint8_t const * tud_descriptor_device_cb(void) #define EPNUM_AUDIO_OUT 0x0A #define EPNUM_AUDIO_IN 0x0B #define EPNUM_AUDIO_INT 0x01 + #elif TU_CHECK_MCU(OPT_MCU_PY32F0) + // Speaker OUT (196 B) only fits EP1 (512 B FIFO); mic IN (98 B) fits EP2 (128 B) + #define EPNUM_AUDIO_OUT 0x01 + #define EPNUM_AUDIO_IN 0x02 + #define EPNUM_AUDIO_INT 0x03 #else #define EPNUM_AUDIO_IN 0x01 #define EPNUM_AUDIO_OUT 0x02 diff --git a/hw/bsp/py32f0/boards/py32f071_dev_board/board.cmake b/hw/bsp/py32f0/boards/py32f071_dev_board/board.cmake index 2ecf476bd..e759f4af1 100644 --- a/hw/bsp/py32f0/boards/py32f071_dev_board/board.cmake +++ b/hw/bsp/py32f0/boards/py32f071_dev_board/board.cmake @@ -5,6 +5,7 @@ set(LD_FILE_GNU ${CMAKE_CURRENT_LIST_DIR}/py32f071xb.ld) function(update_board TARGET) target_compile_definitions(${TARGET} PUBLIC PY32F071xB + CFG_EXAMPLE_MSC_READONLY CFG_EXAMPLE_MSC_DUAL_READONLY CFG_EXAMPLE_VIDEO_READONLY ) diff --git a/hw/bsp/py32f0/boards/py32f071_dev_board/board.mk b/hw/bsp/py32f0/boards/py32f071_dev_board/board.mk index 00003f364..e7fbc338d 100644 --- a/hw/bsp/py32f0/boards/py32f071_dev_board/board.mk +++ b/hw/bsp/py32f0/boards/py32f071_dev_board/board.mk @@ -2,6 +2,7 @@ PY32_SERIES = PY32F071 CFLAGS += \ -DPY32F071xB \ + -DCFG_EXAMPLE_MSC_READONLY \ -DCFG_EXAMPLE_MSC_DUAL_READONLY \ -DCFG_EXAMPLE_VIDEO_READONLY diff --git a/src/portable/mentor/musb/dcd_musb.c b/src/portable/mentor/musb/dcd_musb.c index 69b7ef0de..7d79a089a 100644 --- a/src/portable/mentor/musb/dcd_musb.c +++ b/src/portable/mentor/musb/dcd_musb.c @@ -235,7 +235,10 @@ TU_ATTR_ALWAYS_INLINE static inline bool hwfifo_config(musb_regs_t* musb, unsign (void) mps; #if defined(TUP_USBIP_MUSB_PY32) - (void) musb; (void) epnum; (void) is_rx; (void) double_packet; + (void) musb; (void) epnum; (void) is_rx; (void) mps; (void) double_packet; + // Puya FIFO sizes: EP0 = 64 B, EP1 = 512 B, EP2..4 = 128 B, EP5 = 64 B, shared between IN and OUT. + //static const uint16_t py32_fifo_size[] = { 64, 512, 128, 128, 128, 64 }; + //TU_VERIFY(epnum < TU_ARRAY_SIZE(py32_fifo_size) && mps <= py32_fifo_size[epnum]); #elif defined(TUP_USBIP_MUSB_ADI) // AnalogDevice FIFO sizes: EP1..7 = 512 B, EP8..9 = 2048 B, EP10..11 = 4096 B. // DPB requires FIFO >= 2 * MPS. For HS bulk (MPS=512) only EP >= 8 qualifies. -- cgit v1.3.1 From cd2006382d422eab2e362d364f3b1c60108c73dd Mon Sep 17 00:00:00 2001 From: Jie Feng Date: Mon, 20 Jul 2026 01:15:19 +0800 Subject: return false on too large ep sizes --- src/portable/mentor/musb/dcd_musb.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/portable/mentor/musb/dcd_musb.c b/src/portable/mentor/musb/dcd_musb.c index 7d79a089a..249868b75 100644 --- a/src/portable/mentor/musb/dcd_musb.c +++ b/src/portable/mentor/musb/dcd_musb.c @@ -235,10 +235,10 @@ TU_ATTR_ALWAYS_INLINE static inline bool hwfifo_config(musb_regs_t* musb, unsign (void) mps; #if defined(TUP_USBIP_MUSB_PY32) - (void) musb; (void) epnum; (void) is_rx; (void) mps; (void) double_packet; + (void) musb; (void) is_rx; (void) double_packet; // Puya FIFO sizes: EP0 = 64 B, EP1 = 512 B, EP2..4 = 128 B, EP5 = 64 B, shared between IN and OUT. - //static const uint16_t py32_fifo_size[] = { 64, 512, 128, 128, 128, 64 }; - //TU_VERIFY(epnum < TU_ARRAY_SIZE(py32_fifo_size) && mps <= py32_fifo_size[epnum]); + static const uint16_t py32_fifo_size[] = { 64, 512, 128, 128, 128, 64 }; + return epnum < TU_ARRAY_SIZE(py32_fifo_size) && mps <= py32_fifo_size[epnum]; #elif defined(TUP_USBIP_MUSB_ADI) // AnalogDevice FIFO sizes: EP1..7 = 512 B, EP8..9 = 2048 B, EP10..11 = 4096 B. // DPB requires FIFO >= 2 * MPS. For HS bulk (MPS=512) only EP >= 8 qualifies. -- cgit v1.3.1 From ea88d2f530e11702a7bfc5aa882e8e659a677681 Mon Sep 17 00:00:00 2001 From: HiFiPhile Date: Sun, 19 Jul 2026 20:30:08 +0200 Subject: improve pointer arithmetic, check funmctional descriptor bLength Signed-off-by: HiFiPhile --- src/class/cdc/cdc_host.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/class/cdc/cdc_host.c b/src/class/cdc/cdc_host.c index d1cbc2924..f697c0e73 100644 --- a/src/class/cdc/cdc_host.c +++ b/src/class/cdc/cdc_host.c @@ -1024,6 +1024,8 @@ static uint16_t acm_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, u const uint8_t *p_desc = (const uint8_t *)itf_desc; const uint8_t *desc_end = p_desc + max_len; + TU_ASSERT(TU_DESC_VALIDATE(tu_desc_len(p_desc) <= max_len), 0); + cdch_interface_t *p_cdc = make_new_itf(daddr, itf_desc); TU_VERIFY(p_cdc, 0); p_cdc->serial_drid = SERIAL_DRIVER_ACM; @@ -1033,11 +1035,12 @@ static uint16_t acm_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, u // Communication Functional Descriptors // need the 3-byte header (bLength/bDescriptorType/bDescriptorSubType) in bounds before reading it, and a - // bLength >= 3 both keeps those reads valid and stops a zero-length descriptor from spinning the walk - while ((p_desc < desc_end) && TU_DESC_VALIDATE(p_desc + 3 <= desc_end) && - TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && TU_DESC_VALIDATE(tu_desc_len(p_desc) >= 3)) { + // fully contained bLength >= 3 both keeps those reads valid and stops a zero-length descriptor from spinning + while ((p_desc < desc_end) && TU_DESC_VALIDATE((size_t)(desc_end - p_desc) >= 3) && + TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && TU_DESC_VALIDATE(tu_desc_len(p_desc) >= 3) && + TU_DESC_VALIDATE(tu_desc_len(p_desc) <= (size_t)(desc_end - p_desc))) { if (CDC_FUNC_DESC_ABSTRACT_CONTROL_MANAGEMENT == cdc_functional_desc_typeof(p_desc) && - TU_DESC_VALIDATE(p_desc + sizeof(cdc_desc_func_acm_t) <= desc_end)) { + TU_DESC_VALIDATE(tu_desc_len(p_desc) >= sizeof(cdc_desc_func_acm_t))) { // save ACM bmCapabilities p_cdc->acm.capability = ((cdc_desc_func_acm_t const *) p_desc)->bmCapabilities; } @@ -1048,7 +1051,7 @@ static uint16_t acm_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, u // Open notification endpoint of control interface if any if (itf_desc->bNumEndpoints == 1) { // whole endpoint descriptor must fit: tuh_edpt_open reads the full struct regardless of bLength - TU_ASSERT(TU_DESC_VALIDATE(p_desc + sizeof(tusb_desc_endpoint_t) <= desc_end), 0); + TU_ASSERT(TU_DESC_VALIDATE((size_t)(desc_end - p_desc) >= sizeof(tusb_desc_endpoint_t)), 0); TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(p_desc), 0); const tusb_desc_endpoint_t *desc_ep = (const tusb_desc_endpoint_t *)p_desc; TU_ASSERT(tuh_edpt_open(daddr, desc_ep), 0); @@ -1059,7 +1062,7 @@ static uint16_t acm_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, u } //------------- Data Interface (if any) -------------// - if (TU_DESC_VALIDATE(p_desc + sizeof(tusb_desc_interface_t) <= desc_end) && + if (TU_DESC_VALIDATE((size_t)(desc_end - p_desc) >= sizeof(tusb_desc_interface_t)) && TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) { const tusb_desc_interface_t *data_itf = (const tusb_desc_interface_t *)p_desc; if (data_itf->bInterfaceClass == TUSB_CLASS_CDC_DATA) { @@ -1067,7 +1070,7 @@ static uint16_t acm_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, u // open_ep_stream_pair consumes exactly two endpoints; require that count and that both fit before reading them TU_ASSERT(TU_DESC_VALIDATE(data_itf->bNumEndpoints == 2), 0); - TU_ASSERT(TU_DESC_VALIDATE(p_desc + 2 * sizeof(tusb_desc_endpoint_t) <= desc_end), 0); + TU_ASSERT(TU_DESC_VALIDATE((size_t)(desc_end - p_desc) >= 2 * sizeof(tusb_desc_endpoint_t)), 0); TU_ASSERT(open_ep_stream_pair(p_cdc, (const tusb_desc_endpoint_t *)p_desc), 0); p_desc += 2 * sizeof(tusb_desc_endpoint_t); } -- cgit v1.3.1 From 1c3470a71e3755e6919cb7bd1a5d07f1985c8bb9 Mon Sep 17 00:00:00 2001 From: HiFiPhile Date: Sun, 19 Jul 2026 20:39:02 +0200 Subject: improve validation structure Signed-off-by: HiFiPhile --- src/class/cdc/cdc_host.c | 30 +++++++++++--------------- src/tusb_option.h | 56 ++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 59 insertions(+), 27 deletions(-) (limited to 'src') diff --git a/src/class/cdc/cdc_host.c b/src/class/cdc/cdc_host.c index f697c0e73..5c6890d66 100644 --- a/src/class/cdc/cdc_host.c +++ b/src/class/cdc/cdc_host.c @@ -715,18 +715,10 @@ bool cdch_xfer_cb(uint8_t daddr, uint8_t ep_addr, xfer_result_t event, uint32_t // Enumeration //--------------------------------------------------------------------+ - // Descriptor-walk hardening gated by CFG_TUH_VALIDATION_LEVEL: at NONE the guards collapse to a pass so - // trusted-device setups pay no code size; at BASIC (default) the walk stays inside the enumeration buffer. - #if CFG_TUH_VALIDATION_LEVEL >= TUSB_VALIDATION_BASIC - #define TU_DESC_VALIDATE(_cond) (_cond) - #else - #define TU_DESC_VALIDATE(_cond) (true) - #endif - static bool open_ep_stream_pair(cdch_interface_t *p_cdc, tusb_desc_endpoint_t const *desc_ep) { for (size_t i = 0; i < 2; i++) { // pin bLength so tu_desc_next() below cannot walk the second endpoint past a caller-checked bound - TU_ASSERT(TU_DESC_VALIDATE(sizeof(tusb_desc_endpoint_t) == desc_ep->bLength) && + TU_ASSERT(TUH_VALIDATE_BASIC(sizeof(tusb_desc_endpoint_t) == desc_ep->bLength) && TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && TUSB_XFER_BULK == desc_ep->bmAttributes.xfer, 0); TU_ASSERT(tuh_edpt_open(p_cdc->daddr, desc_ep)); @@ -1024,7 +1016,7 @@ static uint16_t acm_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, u const uint8_t *p_desc = (const uint8_t *)itf_desc; const uint8_t *desc_end = p_desc + max_len; - TU_ASSERT(TU_DESC_VALIDATE(tu_desc_len(p_desc) <= max_len), 0); + TU_ASSERT(TUH_VALIDATE_BASIC(tu_desc_len(p_desc) <= max_len), 0); cdch_interface_t *p_cdc = make_new_itf(daddr, itf_desc); TU_VERIFY(p_cdc, 0); @@ -1036,11 +1028,13 @@ static uint16_t acm_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, u // Communication Functional Descriptors // need the 3-byte header (bLength/bDescriptorType/bDescriptorSubType) in bounds before reading it, and a // fully contained bLength >= 3 both keeps those reads valid and stops a zero-length descriptor from spinning - while ((p_desc < desc_end) && TU_DESC_VALIDATE((size_t)(desc_end - p_desc) >= 3) && - TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && TU_DESC_VALIDATE(tu_desc_len(p_desc) >= 3) && - TU_DESC_VALIDATE(tu_desc_len(p_desc) <= (size_t)(desc_end - p_desc))) { + while ((p_desc < desc_end) && + TUH_VALIDATE_BASIC((size_t)(desc_end - p_desc) >= 3) && + TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && + TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= 3) && + TUH_VALIDATE_BASIC(tu_desc_len(p_desc) <= (size_t)(desc_end - p_desc))) { if (CDC_FUNC_DESC_ABSTRACT_CONTROL_MANAGEMENT == cdc_functional_desc_typeof(p_desc) && - TU_DESC_VALIDATE(tu_desc_len(p_desc) >= sizeof(cdc_desc_func_acm_t))) { + TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= sizeof(cdc_desc_func_acm_t))) { // save ACM bmCapabilities p_cdc->acm.capability = ((cdc_desc_func_acm_t const *) p_desc)->bmCapabilities; } @@ -1051,7 +1045,7 @@ static uint16_t acm_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, u // Open notification endpoint of control interface if any if (itf_desc->bNumEndpoints == 1) { // whole endpoint descriptor must fit: tuh_edpt_open reads the full struct regardless of bLength - TU_ASSERT(TU_DESC_VALIDATE((size_t)(desc_end - p_desc) >= sizeof(tusb_desc_endpoint_t)), 0); + TU_ASSERT(TUH_VALIDATE_BASIC((size_t)(desc_end - p_desc) >= sizeof(tusb_desc_endpoint_t)), 0); TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(p_desc), 0); const tusb_desc_endpoint_t *desc_ep = (const tusb_desc_endpoint_t *)p_desc; TU_ASSERT(tuh_edpt_open(daddr, desc_ep), 0); @@ -1062,15 +1056,15 @@ static uint16_t acm_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, u } //------------- Data Interface (if any) -------------// - if (TU_DESC_VALIDATE((size_t)(desc_end - p_desc) >= sizeof(tusb_desc_interface_t)) && + if (TUH_VALIDATE_BASIC((size_t)(desc_end - p_desc) >= sizeof(tusb_desc_interface_t)) && TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) { const tusb_desc_interface_t *data_itf = (const tusb_desc_interface_t *)p_desc; if (data_itf->bInterfaceClass == TUSB_CLASS_CDC_DATA) { p_desc += sizeof(tusb_desc_interface_t); // fixed struct size to endpoint descriptor, not device bLength // open_ep_stream_pair consumes exactly two endpoints; require that count and that both fit before reading them - TU_ASSERT(TU_DESC_VALIDATE(data_itf->bNumEndpoints == 2), 0); - TU_ASSERT(TU_DESC_VALIDATE((size_t)(desc_end - p_desc) >= 2 * sizeof(tusb_desc_endpoint_t)), 0); + TU_ASSERT(TUH_VALIDATE_BASIC(data_itf->bNumEndpoints == 2), 0); + TU_ASSERT(TUH_VALIDATE_BASIC((size_t)(desc_end - p_desc) >= 2 * sizeof(tusb_desc_endpoint_t)), 0); TU_ASSERT(open_ep_stream_pair(p_cdc, (const tusb_desc_endpoint_t *)p_desc), 0); p_desc += 2 * sizeof(tusb_desc_endpoint_t); } diff --git a/src/tusb_option.h b/src/tusb_option.h index de8439cfb..75c8ff93d 100644 --- a/src/tusb_option.h +++ b/src/tusb_option.h @@ -240,13 +240,14 @@ #define OPT_MODE_SPEED_MASK 0xff00u //--------------------------------------------------------------------+ -// Descriptor Validation Level -// How much the stack hardens itself against mal-configured or hostile devices, traded against code size. -// Higher levels add more checks; set CFG_TUD_VALIDATION_LEVEL / CFG_TUH_VALIDATION_LEVEL to pick one. +// Validation Level +// Optional validation of data received from the USB peer, traded against code size. Coverage is parser-specific +// and expanded incrementally. CFG_TUSB_VALIDATION_LEVEL sets the default for both device and host; use the +// CFG_TUD_VALIDATION_LEVEL / CFG_TUH_VALIDATION_LEVEL overrides when the roles need different policies. //--------------------------------------------------------------------+ -#define TUSB_VALIDATION_NONE 0 ///< trusted devices only, minimal code size -#define TUSB_VALIDATION_BASIC 1 ///< default: mal-configured devices, no OOB reads / zero-length loops -#define TUSB_VALIDATION_STRICT 2 ///< reject malformed/hostile descriptors, stricter class validation +#define TUSB_VALIDATION_NONE 0 ///< trusted peers, minimal code size +#define TUSB_VALIDATION_BASIC 1 ///< structural memory-safety checks where supported +#define TUSB_VALIDATION_STRICT 2 ///< additional USB and class-specific conformance checks //--------------------------------------------------------------------+ // Include tusb_config.h @@ -261,6 +262,46 @@ #include "common/tusb_mcu.h" +//--------------------------------------------------------------------+ +// Validation Options +//--------------------------------------------------------------------+ + +#ifndef CFG_TUSB_VALIDATION_LEVEL + #define CFG_TUSB_VALIDATION_LEVEL TUSB_VALIDATION_BASIC +#endif + +#ifndef CFG_TUD_VALIDATION_LEVEL + #define CFG_TUD_VALIDATION_LEVEL CFG_TUSB_VALIDATION_LEVEL +#endif + +#ifndef CFG_TUH_VALIDATION_LEVEL + #define CFG_TUH_VALIDATION_LEVEL CFG_TUSB_VALIDATION_LEVEL +#endif + +#if (CFG_TUSB_VALIDATION_LEVEL < TUSB_VALIDATION_NONE) || \ + (CFG_TUSB_VALIDATION_LEVEL > TUSB_VALIDATION_STRICT) + #error "CFG_TUSB_VALIDATION_LEVEL must be TUSB_VALIDATION_NONE, TUSB_VALIDATION_BASIC, or TUSB_VALIDATION_STRICT" +#endif + +#if (CFG_TUD_VALIDATION_LEVEL < TUSB_VALIDATION_NONE) || \ + (CFG_TUD_VALIDATION_LEVEL > TUSB_VALIDATION_STRICT) + #error "CFG_TUD_VALIDATION_LEVEL must be TUSB_VALIDATION_NONE, TUSB_VALIDATION_BASIC, or TUSB_VALIDATION_STRICT" +#endif + +#if (CFG_TUH_VALIDATION_LEVEL < TUSB_VALIDATION_NONE) || \ + (CFG_TUH_VALIDATION_LEVEL > TUSB_VALIDATION_STRICT) + #error "CFG_TUH_VALIDATION_LEVEL must be TUSB_VALIDATION_NONE, TUSB_VALIDATION_BASIC, or TUSB_VALIDATION_STRICT" +#endif + +// Validation conditions are short-circuited at lower levels and compile out when the result is unused. +#define TUD_VALIDATION_CHECK(_level, _cond) ((CFG_TUD_VALIDATION_LEVEL < (_level)) || (_cond)) +#define TUH_VALIDATION_CHECK(_level, _cond) ((CFG_TUH_VALIDATION_LEVEL < (_level)) || (_cond)) + +#define TUD_VALIDATE_BASIC(_cond) TUD_VALIDATION_CHECK(TUSB_VALIDATION_BASIC, _cond) +#define TUH_VALIDATE_BASIC(_cond) TUH_VALIDATION_CHECK(TUSB_VALIDATION_BASIC, _cond) +#define TUD_VALIDATE_STRICT(_cond) TUD_VALIDATION_CHECK(TUSB_VALIDATION_STRICT, _cond) +#define TUH_VALIDATE_STRICT(_cond) TUH_VALIDATION_CHECK(TUSB_VALIDATION_STRICT, _cond) + //--------------------------------------------------------------------+ // USBIP //--------------------------------------------------------------------+ @@ -704,9 +745,6 @@ #define CFG_TUH_ENUMERATION_BUFSIZE 256 #endif - #ifndef CFG_TUH_VALIDATION_LEVEL - #define CFG_TUH_VALIDATION_LEVEL TUSB_VALIDATION_BASIC - #endif #endif // CFG_TUH_ENABLED // Attribute to place data in accessible RAM for host controller (default: CFG_TUSB_MEM_SECTION) -- cgit v1.3.1 From 17aee71c1105763e99248737532411a936703f47 Mon Sep 17 00:00:00 2001 From: HiFiPhile Date: Sun, 19 Jul 2026 21:11:12 +0200 Subject: add interface desc length check Signed-off-by: HiFiPhile --- src/class/cdc/cdc_host.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/class/cdc/cdc_host.c b/src/class/cdc/cdc_host.c index 5c6890d66..c9d2a8dd1 100644 --- a/src/class/cdc/cdc_host.c +++ b/src/class/cdc/cdc_host.c @@ -1208,6 +1208,7 @@ static uint16_t ftdi_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, TU_VERIFY(itf_desc->bInterfaceSubClass == 0xff && itf_desc->bInterfaceProtocol == 0xff && itf_desc->bNumEndpoints == 2, 0); + TU_VERIFY(TUH_VALIDATE_BASIC(itf_desc->bLength == sizeof(tusb_desc_interface_t)), 0); const uint16_t drv_len = (uint16_t)(sizeof(tusb_desc_interface_t) + itf_desc->bNumEndpoints * sizeof(tusb_desc_endpoint_t)); TU_VERIFY(drv_len <= max_len, 0); @@ -1593,6 +1594,7 @@ enum { static uint16_t cp210x_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, uint16_t max_len) { // CP210x Interface includes 1 vendor interface + 2 bulk endpoints TU_VERIFY(itf_desc->bInterfaceSubClass == 0 && itf_desc->bInterfaceProtocol == 0 && itf_desc->bNumEndpoints == 2, 0); + TU_VERIFY(TUH_VALIDATE_BASIC(itf_desc->bLength == sizeof(tusb_desc_interface_t)), 0); const uint16_t drv_len = (uint16_t)(sizeof(tusb_desc_interface_t) + itf_desc->bNumEndpoints * sizeof(tusb_desc_endpoint_t)); TU_VERIFY(drv_len <= max_len, 0); @@ -1764,6 +1766,7 @@ enum { static uint16_t ch34x_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, uint16_t max_len) { // CH34x Interface includes 1 vendor interface + 2 bulk + 1 interrupt endpoints TU_VERIFY(itf_desc->bNumEndpoints == 3, 0); + TU_VERIFY(TUH_VALIDATE_BASIC(itf_desc->bLength == sizeof(tusb_desc_interface_t)), 0); const uint16_t drv_len = (uint16_t)(sizeof(tusb_desc_interface_t) + itf_desc->bNumEndpoints * sizeof(tusb_desc_endpoint_t)); TU_VERIFY(drv_len <= max_len, 0); @@ -2100,6 +2103,7 @@ enum { static uint16_t pl2303_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, uint16_t max_len) { // PL2303 Interface includes 1 vendor interface + 1 interrupt endpoints + 2 bulk TU_VERIFY(itf_desc->bNumEndpoints == 3, 0); + TU_VERIFY(TUH_VALIDATE_BASIC(itf_desc->bLength == sizeof(tusb_desc_interface_t)), 0); const uint16_t drv_len = (uint16_t)(sizeof(tusb_desc_interface_t) + itf_desc->bNumEndpoints * sizeof(tusb_desc_endpoint_t)); TU_VERIFY(drv_len <= max_len, 0); -- cgit v1.3.1 From b00b40da28285f67efd652e20cfe3877d42d2ff3 Mon Sep 17 00:00:00 2001 From: HiFiPhile Date: Sun, 19 Jul 2026 21:13:21 +0200 Subject: add assert to dcd_edpt_iso_alloc Signed-off-by: HiFiPhile --- src/portable/mentor/musb/dcd_musb.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/portable/mentor/musb/dcd_musb.c b/src/portable/mentor/musb/dcd_musb.c index 249868b75..92c9fe92e 100644 --- a/src/portable/mentor/musb/dcd_musb.c +++ b/src/portable/mentor/musb/dcd_musb.c @@ -823,7 +823,8 @@ bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet pipe_state_t *pipe = pipe_get(epn, dir_in); pipe->mps = largest_packet_size; ep_csr->maxp_csr[is_rx].csrh = 0; - return hwfifo_config(musb, epn, is_rx, largest_packet_size, true); + TU_ASSERT(hwfifo_config(musb, epn, is_rx, largest_packet_size, true)); + return true; } bool dcd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const *ep_desc ) { -- cgit v1.3.1 From a3e58adf6008f3199cf653859bea4dc73217a55a Mon Sep 17 00:00:00 2001 From: Zixun LI Date: Sun, 19 Jul 2026 22:16:33 +0200 Subject: Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/portable/st/stm32_fsdev/fsdev_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/portable/st/stm32_fsdev/fsdev_common.c b/src/portable/st/stm32_fsdev/fsdev_common.c index def3b2c2e..9c9233d12 100644 --- a/src/portable/st/stm32_fsdev/fsdev_common.c +++ b/src/portable/st/stm32_fsdev/fsdev_common.c @@ -36,7 +36,7 @@ void fsdev_core_reset(void) { #if (CFG_TUSB_MCU == OPT_MCU_AT32F403A_407) || (CFG_TUSB_MCU == OPT_MCU_AT32F413) // Enable larger PMA area - CRM->misc1_bit.usbbufs = TRUE; + CRM->misc1_bit.usbbufs = 1; #endif } -- cgit v1.3.1 From c60e6005faa469adbb583f489d4a9c7b6c3c2f5c Mon Sep 17 00:00:00 2001 From: Jie Feng Date: Sat, 2 May 2026 12:42:07 +0800 Subject: Add support for APM32F072 --- .../boards/apm32f072_dev_board/board.cmake | 8 ++ .../apm32f0xx/boards/apm32f072_dev_board/board.h | 49 +++++++ .../apm32f0xx/boards/apm32f072_dev_board/board.mk | 7 + hw/bsp/apm32f0xx/family.c | 145 +++++++++++++++++++++ hw/bsp/apm32f0xx/family.cmake | 84 ++++++++++++ hw/bsp/apm32f0xx/family.mk | 39 ++++++ src/common/tusb_mcu.h | 8 ++ src/portable/st/stm32_fsdev/fsdev_apm32.h | 77 +++++++++++ src/portable/st/stm32_fsdev/fsdev_common.h | 2 + src/tusb_option.h | 3 + 10 files changed, 422 insertions(+) create mode 100644 hw/bsp/apm32f0xx/boards/apm32f072_dev_board/board.cmake create mode 100644 hw/bsp/apm32f0xx/boards/apm32f072_dev_board/board.h create mode 100644 hw/bsp/apm32f0xx/boards/apm32f072_dev_board/board.mk create mode 100644 hw/bsp/apm32f0xx/family.c create mode 100644 hw/bsp/apm32f0xx/family.cmake create mode 100644 hw/bsp/apm32f0xx/family.mk create mode 100644 src/portable/st/stm32_fsdev/fsdev_apm32.h (limited to 'src') diff --git a/hw/bsp/apm32f0xx/boards/apm32f072_dev_board/board.cmake b/hw/bsp/apm32f0xx/boards/apm32f072_dev_board/board.cmake new file mode 100644 index 000000000..33148dbd4 --- /dev/null +++ b/hw/bsp/apm32f0xx/boards/apm32f072_dev_board/board.cmake @@ -0,0 +1,8 @@ +set(MCU_VARIANT APM32F072xB) +set(MCU_LINKER_NAME APM32F07xxB) + +set(JLINK_DEVICE APM32F072RB) + +function(update_board TARGET) + target_compile_definitions(${TARGET} PUBLIC ${MCU_VARIANT}) +endfunction() diff --git a/hw/bsp/apm32f0xx/boards/apm32f072_dev_board/board.h b/hw/bsp/apm32f0xx/boards/apm32f072_dev_board/board.h new file mode 100644 index 000000000..2c9e567e5 --- /dev/null +++ b/hw/bsp/apm32f0xx/boards/apm32f072_dev_board/board.h @@ -0,0 +1,49 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024, Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +/* metadata: + name: APM32F072 Dev Board + url: https://www.geehy.com +*/ + +#ifndef BOARD_H_ +#define BOARD_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +// LED +#define LED_PORT GPIOC +#define LED_PIN GPIO_PIN_13 +#define LED_STATE_ON 0 +#define LED_GPIO_CLK_EN() RCM_EnableAHBPeriphClock(RCM_AHB_PERIPH_GPIOC) + +#ifdef __cplusplus + } +#endif + +#endif /* BOARD_H_ */ diff --git a/hw/bsp/apm32f0xx/boards/apm32f072_dev_board/board.mk b/hw/bsp/apm32f0xx/boards/apm32f072_dev_board/board.mk new file mode 100644 index 000000000..2e5df9947 --- /dev/null +++ b/hw/bsp/apm32f0xx/boards/apm32f072_dev_board/board.mk @@ -0,0 +1,7 @@ +MCU_VARIANT = APM32F072xB +MCU_LINKER_NAME = APM32F07xxB + +JLINK_DEVICE = APM32F072RB + +CFLAGS += \ + -D${MCU_VARIANT} diff --git a/hw/bsp/apm32f0xx/family.c b/hw/bsp/apm32f0xx/family.c new file mode 100644 index 000000000..cbc427f8c --- /dev/null +++ b/hw/bsp/apm32f0xx/family.c @@ -0,0 +1,145 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +/* metadata: + manufacturer: Geehy +*/ + +#include "apm32f0xx.h" +#include "apm32f0xx_rcm.h" +#include "apm32f0xx_gpio.h" +#include "apm32f0xx_misc.h" +#include "apm32f0xx_crs.h" +#include "bsp/board_api.h" +#include "board.h" + +//--------------------------------------------------------------------+ +// Forward USB interrupt events to TinyUSB IRQ Handler +//--------------------------------------------------------------------+ +void USBD_IRQHandler(void) { + tud_int_handler(0); +} + +//--------------------------------------------------------------------+ +// Board Init +//--------------------------------------------------------------------+ +void board_init(void) { + // Enable HSI48 for USB clock + RCM_EnableHSI48(); + while (RCM_ReadStatusFlag(RCM_FLAG_HSI48RDY) == RESET) {} + + // Select HSI48 as USB clock source + RCM_ConfigUSBCLK(RCM_USBCLK_HSI48); + + // Enable CRS for automatic HSI48 calibration from USB SOF + RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_CRS); + CRS_ConfigSynchronizationSource(CRS_SYNC_SOURCE_USB); + CRS_EnableAutomaticCalibration(); + CRS_EnableFrequencyErrorCounter(); + + // Enable USB peripheral clock + RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_USB); + + // SysTick 1ms tick + SysTick_Config(SystemCoreClock / 1000); + + // LED + LED_GPIO_CLK_EN(); + GPIO_Config_T gpio_config; + GPIO_ConfigStructInit(&gpio_config); + gpio_config.pin = LED_PIN; + gpio_config.mode = GPIO_MODE_OUT; + gpio_config.outtype = GPIO_OUT_TYPE_PP; + gpio_config.speed = GPIO_SPEED_50MHz; + GPIO_Config(LED_PORT, &gpio_config); + + board_led_write(false); +} + +//--------------------------------------------------------------------+ +// Board porting API +//--------------------------------------------------------------------+ +void board_led_write(bool state) { + if (state ^ (!LED_STATE_ON)) { + GPIO_SetBit(LED_PORT, LED_PIN); + } else { + GPIO_ClearBit(LED_PORT, LED_PIN); + } +} + +uint32_t board_button_read(void) { + return 0; +} + +size_t board_get_unique_id(uint8_t id[], size_t max_len) { + (void) max_len; + volatile uint32_t *apm32_uuid = ((volatile uint32_t *) 0x1FFFF7AC); + uint32_t *id32 = (uint32_t *) (uintptr_t) id; + uint8_t const len = 12; + + id32[0] = apm32_uuid[0]; + id32[1] = apm32_uuid[1]; + id32[2] = apm32_uuid[2]; + + return len; +} + +int board_uart_read(uint8_t *buf, int len) { + (void) buf; + (void) len; + return 0; +} + +int board_uart_write(void const *buf, int len) { + (void) buf; + (void) len; + return 0; +} + +#if CFG_TUSB_OS == OPT_OS_NONE +volatile uint32_t system_ticks = 0; + +void SysTick_Handler(void) { + system_ticks++; +} + +uint32_t tusb_time_millis_api(void) { + return system_ticks; +} + +void SVC_Handler(void) { +} + +void PendSV_Handler(void) { +} +#endif + +void HardFault_Handler(void) { + __asm("BKPT #0\n"); +} + +void _init(void) { +} diff --git a/hw/bsp/apm32f0xx/family.cmake b/hw/bsp/apm32f0xx/family.cmake new file mode 100644 index 000000000..99a94a7a8 --- /dev/null +++ b/hw/bsp/apm32f0xx/family.cmake @@ -0,0 +1,84 @@ +include_guard() + +set(APM32_FAMILY apm32f0xx) +set(APM32_SDK ${TOP}/hw/mcu/geehy/APM32F0xx_SDK_V1.8.6/Libraries) + +# include board specific +include(${CMAKE_CURRENT_LIST_DIR}/boards/${BOARD}/board.cmake) + +# toolchain set up +set(CMAKE_SYSTEM_CPU cortex-m0plus CACHE INTERNAL "System Processor") +set(CMAKE_TOOLCHAIN_FILE ${TOP}/examples/build_system/cmake/toolchain/arm_${TOOLCHAIN}.cmake) + +set(FAMILY_MCUS APM32F0XX CACHE INTERNAL "") + +#------------------------------------ +# Startup & Linker script +#------------------------------------ +set(STARTUP_FILE_GNU ${APM32_SDK}/Device/Geehy/APM32F0xx/Source/gcc/startup_apm32f072.S) +set(STARTUP_FILE_Clang ${STARTUP_FILE_GNU}) +if (NOT DEFINED LD_FILE_GNU) +set(LD_FILE_GNU ${APM32_SDK}/Device/Geehy/APM32F0xx/Source/gcc/gcc_${MCU_LINKER_NAME}.ld) +endif () +set(LD_FILE_Clang ${LD_FILE_GNU}) + +#------------------------------------ +# BOARD_TARGET +#------------------------------------ +function(family_add_board BOARD_TARGET) + add_library(${BOARD_TARGET} STATIC + ${APM32_SDK}/Device/Geehy/APM32F0xx/Source/system_apm32f0xx.c + ${APM32_SDK}/APM32F0xx_StdPeriphDriver/src/apm32f0xx_gpio.c + ${APM32_SDK}/APM32F0xx_StdPeriphDriver/src/apm32f0xx_misc.c + ${APM32_SDK}/APM32F0xx_StdPeriphDriver/src/apm32f0xx_rcm.c + ${APM32_SDK}/APM32F0xx_StdPeriphDriver/src/apm32f0xx_crs.c + ) + target_include_directories(${BOARD_TARGET} PUBLIC + ${CMAKE_CURRENT_FUNCTION_LIST_DIR} + ${APM32_SDK}/CMSIS/Include + ${APM32_SDK}/Device/Geehy/APM32F0xx/Include + ${APM32_SDK}/APM32F0xx_StdPeriphDriver/inc + ) + + update_board(${BOARD_TARGET}) +endfunction() + +#------------------------------------ +# Functions +#------------------------------------ +function(family_configure_example TARGET RTOS) + family_configure_common(${TARGET} ${RTOS}) + family_add_tinyusb(${TARGET} OPT_MCU_APM32F0XX) + + target_sources(${TARGET} PUBLIC + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/family.c + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../board.c + ${TOP}/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c + ${TOP}/src/portable/st/stm32_fsdev/fsdev_common.c + ${STARTUP_FILE_${CMAKE_C_COMPILER_ID}} + ) + target_include_directories(${TARGET} PUBLIC + ${CMAKE_CURRENT_FUNCTION_LIST_DIR} + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../../ + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/boards/${BOARD} + ) + + if (CMAKE_C_COMPILER_ID STREQUAL "GNU") + target_link_options(${TARGET} PUBLIC + "LINKER:--script=${LD_FILE_GNU}" + -nostartfiles + --specs=nosys.specs --specs=nano.specs + ) + elseif (CMAKE_C_COMPILER_ID STREQUAL "Clang") + target_link_options(${TARGET} PUBLIC + "LINKER:--script=${LD_FILE_Clang}" + ) + endif () + set_source_files_properties(${STARTUP_FILE_${CMAKE_C_COMPILER_ID}} PROPERTIES + SKIP_LINTING ON + COMPILE_OPTIONS -w) + + # Flashing + family_add_bin_hex(${TARGET}) + family_flash_jlink(${TARGET}) +endfunction() diff --git a/hw/bsp/apm32f0xx/family.mk b/hw/bsp/apm32f0xx/family.mk new file mode 100644 index 000000000..73a762d69 --- /dev/null +++ b/hw/bsp/apm32f0xx/family.mk @@ -0,0 +1,39 @@ +APM32_FAMILY = apm32f0xx +APM32_SDK = hw/mcu/geehy/APM32F0xx_SDK_V1.8.6/Libraries + +include $(TOP)/$(BOARD_PATH)/board.mk + +CPU_CORE ?= cortex-m0plus + +CFLAGS += \ + -flto + +CFLAGS += \ + -DCFG_TUSB_MCU=OPT_MCU_APM32F0XX + +LDFLAGS += \ + -flto --specs=nosys.specs -nostdlib -nostartfiles + +SRC_C += \ + src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c \ + src/portable/st/stm32_fsdev/fsdev_common.c \ + $(APM32_SDK)/APM32F0xx_StdPeriphDriver/src/apm32f0xx_gpio.c \ + $(APM32_SDK)/APM32F0xx_StdPeriphDriver/src/apm32f0xx_misc.c \ + $(APM32_SDK)/APM32F0xx_StdPeriphDriver/src/apm32f0xx_rcm.c \ + $(APM32_SDK)/APM32F0xx_StdPeriphDriver/src/apm32f0xx_crs.c \ + $(APM32_SDK)/Device/Geehy/APM32F0xx/Source/system_apm32f0xx.c + +INC += \ + $(TOP)/$(BOARD_PATH) \ + $(TOP)/$(APM32_SDK)/APM32F0xx_StdPeriphDriver/inc \ + $(TOP)/$(APM32_SDK)/CMSIS/Include \ + $(TOP)/$(APM32_SDK)/Device/Geehy/APM32F0xx/Include + +SRC_S += $(APM32_SDK)/Device/Geehy/APM32F0xx/Source/gcc/startup_apm32f072.S + +LD_FILE ?= $(APM32_SDK)/Device/Geehy/APM32F0xx/Source/gcc/gcc_${MCU_LINKER_NAME}.ld + +# For freeRTOS port source +FREERTOS_PORTABLE_SRC = $(FREERTOS_PORTABLE_PATH)/ARM_CM0 + +flash: flash-jlink diff --git a/src/common/tusb_mcu.h b/src/common/tusb_mcu.h index b8d883dde..93b4a2ee9 100644 --- a/src/common/tusb_mcu.h +++ b/src/common/tusb_mcu.h @@ -716,6 +716,14 @@ // Possible to share IN/OUT if only one direction is armed at any one time #define CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY 1 +//--------------------------------------------------------------------+ +// Geehy +//--------------------------------------------------------------------+ +#elif TU_CHECK_MCU(OPT_MCU_APM32F0XX) + #define TUP_USBIP_FSDEV + #define TUP_USBIP_FSDEV_APM32 + #define CFG_TUSB_FSDEV_PMA_SIZE 1024u + #endif // External USB controller diff --git a/src/portable/st/stm32_fsdev/fsdev_apm32.h b/src/portable/st/stm32_fsdev/fsdev_apm32.h new file mode 100644 index 000000000..5031445e8 --- /dev/null +++ b/src/portable/st/stm32_fsdev/fsdev_apm32.h @@ -0,0 +1,77 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2024, hathach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ +#ifndef TUSB_FSDEV_APM32_H +#define TUSB_FSDEV_APM32_H + +#include "common/tusb_compiler.h" + +#if CFG_TUSB_MCU == OPT_MCU_APM32F0XX + #include "apm32f0xx.h" +#endif + +#define FSDEV_USE_SBUF_ISO 0 +#define FSDEV_REG_BASE ((uint32_t)(USBD_BASE)) +#define FSDEV_PMA_BASE ((uint32_t)(USBD_BASE + 0x400UL)) + +#ifndef CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP + #define CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP 0 +#endif + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ + +static const IRQn_Type fsdev_irq[] = { + USBD_IRQn +}; +enum { FSDEV_IRQ_NUM = TU_ARRAY_SIZE(fsdev_irq) }; + +TU_ATTR_ALWAYS_INLINE static inline void fsdev_int_enable(uint8_t rhport) { + (void)rhport; + for (uint8_t i = 0; i < FSDEV_IRQ_NUM; i++) { + NVIC_EnableIRQ(fsdev_irq[i]); + } +} + +TU_ATTR_ALWAYS_INLINE static inline void fsdev_int_disable(uint8_t rhport) { + (void)rhport; + for (uint8_t i = 0; i < FSDEV_IRQ_NUM; i++) { + NVIC_DisableIRQ(fsdev_irq[i]); + } +} + +TU_ATTR_ALWAYS_INLINE static inline void fsdev_disconnect(uint8_t rhport) { + (void) rhport; + FSDEV_REG->CNTR |= U_CNTR_PDWN; + FSDEV_REG->BCDR &= ~U_BCDR_DPPU; +} + +TU_ATTR_ALWAYS_INLINE static inline void fsdev_connect(uint8_t rhport) { + (void) rhport; + FSDEV_REG->CNTR &= ~U_CNTR_PDWN; + FSDEV_REG->BCDR |= U_BCDR_DPPU; +} + +#endif diff --git a/src/portable/st/stm32_fsdev/fsdev_common.h b/src/portable/st/stm32_fsdev/fsdev_common.h index ebde44bf7..00d9b513a 100644 --- a/src/portable/st/stm32_fsdev/fsdev_common.h +++ b/src/portable/st/stm32_fsdev/fsdev_common.h @@ -284,6 +284,8 @@ typedef struct { #include "fsdev_ch32.h" #elif defined(TUP_USBIP_FSDEV_AT32) #include "fsdev_at32.h" +#elif defined(TUP_USBIP_FSDEV_APM32) + #include "fsdev_apm32.h" #else #error "Unknown USB IP" #endif diff --git a/src/tusb_option.h b/src/tusb_option.h index b5e910fca..24f802b73 100644 --- a/src/tusb_option.h +++ b/src/tusb_option.h @@ -207,6 +207,9 @@ // Puya #define OPT_MCU_PY32F0 2700 ///< Puya PY32F0 +// Geehy +#define OPT_MCU_APM32F0XX 2800 ///< Geehy APM32F0xx + // Check if configured MCU is one of listed // Apply TU_MCU_IS_EQUAL with || as separator to list of input #define TU_MCU_IS_EQUAL(_m) (CFG_TUSB_MCU == (_m)) -- cgit v1.3.1 From 2d013717a47e7535e1c0663b0300c65b838842d8 Mon Sep 17 00:00:00 2001 From: Anthony VerBurg Date: Wed, 22 Jul 2026 10:57:39 -0700 Subject: Add more HID Usage Page enums --- src/class/hid/hid.h | 2215 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 1584 insertions(+), 631 deletions(-) (limited to 'src') diff --git a/src/class/hid/hid.h b/src/class/hid/hid.h index 50a206ced..da1ebd970 100644 --- a/src/class/hid/hid.h +++ b/src/class/hid/hid.h @@ -368,7 +368,7 @@ typedef enum /// @} //--------------------------------------------------------------------+ -// HID KEYCODE +// HID KEYCODE - defined by HID Usage Table: Keyboard/Keypad Page (0x07) //--------------------------------------------------------------------+ #define HID_KEY_NONE 0x00 #define HID_KEY_A 0x04 @@ -772,11 +772,12 @@ enum { HID_USAGE_PAGE_KEYBOARD = 0x07, HID_USAGE_PAGE_LED = 0x08, HID_USAGE_PAGE_BUTTON = 0x09, - HID_USAGE_PAGE_ORDINAL = 0x0a, - HID_USAGE_PAGE_TELEPHONY = 0x0b, - HID_USAGE_PAGE_CONSUMER = 0x0c, - HID_USAGE_PAGE_DIGITIZER = 0x0d, - HID_USAGE_PAGE_PID = 0x0f, + HID_USAGE_PAGE_ORDINAL = 0x0A, + HID_USAGE_PAGE_TELEPHONY = 0x0B, + HID_USAGE_PAGE_CONSUMER = 0x0C, + HID_USAGE_PAGE_DIGITIZER = 0x0D, + HID_USAGE_PAGE_HAPTIC = 0x0E, + HID_USAGE_PAGE_PID = 0x0F, HID_USAGE_PAGE_UNICODE = 0x10, HID_USAGE_PAGE_SOC = 0x11, HID_USAGE_PAGE_EYE_AND_HEAD_TRACKERS = 0x12, @@ -786,528 +787,1087 @@ enum { HID_USAGE_PAGE_SENSORS = 0x20, // 0x21 - 0x3f is reserved HID_USAGE_PAGE_MEDICAL_INSTRUMENT = 0x40, + HID_USAGE_PAGE_BRAILLE_DISPLAY = 0x41, HID_USAGE_PAGE_LIGHTING_AND_ILLUMINATION = 0x59, HID_USAGE_PAGE_MONITOR = 0x80, // 0x80 - 0x83 HID_USAGE_PAGE_POWER = 0x84, HID_USAGE_PAGE_BATTERY = 0x85, // 0x86 - 0x87 is reserved for Power Device - HID_USAGE_PAGE_BARCODE_SCANNER = 0x8c, - HID_USAGE_PAGE_SCALE = 0x8d, - HID_USAGE_PAGE_MSR = 0x8e, + HID_USAGE_PAGE_BARCODE_SCANNER = 0x8C, + HID_USAGE_PAGE_SCALE = 0x8D, + HID_USAGE_PAGE_MSR = 0x8E, HID_USAGE_PAGE_CAMERA = 0x90, HID_USAGE_PAGE_ARCADE = 0x91, + HID_USAGE_PAGE_GAMING = 0x92, // Gaming Standards Association (GSA) HID usage page HID_USAGE_PAGE_FIDO = 0xF1D0, // FIDO alliance HID usage page HID_USAGE_PAGE_VENDOR = 0xFF00 // 0xFF00 - 0xFFFF }; /// HID Usage Table - Table 6: Generic Desktop Page enum { - HID_USAGE_DESKTOP_POINTER = 0x01, - HID_USAGE_DESKTOP_MOUSE = 0x02, - HID_USAGE_DESKTOP_JOYSTICK = 0x04, - HID_USAGE_DESKTOP_GAMEPAD = 0x05, - HID_USAGE_DESKTOP_KEYBOARD = 0x06, - HID_USAGE_DESKTOP_KEYPAD = 0x07, - HID_USAGE_DESKTOP_MULTI_AXIS_CONTROLLER = 0x08, - HID_USAGE_DESKTOP_TABLET_PC_SYSTEM = 0x09, - HID_USAGE_DESKTOP_X = 0x30, - HID_USAGE_DESKTOP_Y = 0x31, - HID_USAGE_DESKTOP_Z = 0x32, - HID_USAGE_DESKTOP_RX = 0x33, - HID_USAGE_DESKTOP_RY = 0x34, - HID_USAGE_DESKTOP_RZ = 0x35, - HID_USAGE_DESKTOP_SLIDER = 0x36, - HID_USAGE_DESKTOP_DIAL = 0x37, - HID_USAGE_DESKTOP_WHEEL = 0x38, - HID_USAGE_DESKTOP_HAT_SWITCH = 0x39, - HID_USAGE_DESKTOP_COUNTED_BUFFER = 0x3a, - HID_USAGE_DESKTOP_BYTE_COUNT = 0x3b, - HID_USAGE_DESKTOP_MOTION_WAKEUP = 0x3c, - HID_USAGE_DESKTOP_START = 0x3d, - HID_USAGE_DESKTOP_SELECT = 0x3e, - HID_USAGE_DESKTOP_VX = 0x40, - HID_USAGE_DESKTOP_VY = 0x41, - HID_USAGE_DESKTOP_VZ = 0x42, - HID_USAGE_DESKTOP_VBRX = 0x43, - HID_USAGE_DESKTOP_VBRY = 0x44, - HID_USAGE_DESKTOP_VBRZ = 0x45, - HID_USAGE_DESKTOP_VNO = 0x46, - HID_USAGE_DESKTOP_FEATURE_NOTIFICATION = 0x47, - HID_USAGE_DESKTOP_RESOLUTION_MULTIPLIER = 0x48, - HID_USAGE_DESKTOP_SYSTEM_CONTROL = 0x80, - HID_USAGE_DESKTOP_SYSTEM_POWER_DOWN = 0x81, - HID_USAGE_DESKTOP_SYSTEM_SLEEP = 0x82, - HID_USAGE_DESKTOP_SYSTEM_WAKE_UP = 0x83, - HID_USAGE_DESKTOP_SYSTEM_CONTEXT_MENU = 0x84, - HID_USAGE_DESKTOP_SYSTEM_MAIN_MENU = 0x85, - HID_USAGE_DESKTOP_SYSTEM_APP_MENU = 0x86, - HID_USAGE_DESKTOP_SYSTEM_MENU_HELP = 0x87, - HID_USAGE_DESKTOP_SYSTEM_MENU_EXIT = 0x88, - HID_USAGE_DESKTOP_SYSTEM_MENU_SELECT = 0x89, - HID_USAGE_DESKTOP_SYSTEM_MENU_RIGHT = 0x8A, - HID_USAGE_DESKTOP_SYSTEM_MENU_LEFT = 0x8B, - HID_USAGE_DESKTOP_SYSTEM_MENU_UP = 0x8C, - HID_USAGE_DESKTOP_SYSTEM_MENU_DOWN = 0x8D, - HID_USAGE_DESKTOP_SYSTEM_COLD_RESTART = 0x8E, - HID_USAGE_DESKTOP_SYSTEM_WARM_RESTART = 0x8F, - HID_USAGE_DESKTOP_DPAD_UP = 0x90, - HID_USAGE_DESKTOP_DPAD_DOWN = 0x91, - HID_USAGE_DESKTOP_DPAD_RIGHT = 0x92, - HID_USAGE_DESKTOP_DPAD_LEFT = 0x93, - HID_USAGE_DESKTOP_SYSTEM_DOCK = 0xA0, - HID_USAGE_DESKTOP_SYSTEM_UNDOCK = 0xA1, - HID_USAGE_DESKTOP_SYSTEM_SETUP = 0xA2, - HID_USAGE_DESKTOP_SYSTEM_BREAK = 0xA3, - HID_USAGE_DESKTOP_SYSTEM_DEBUGGER_BREAK = 0xA4, - HID_USAGE_DESKTOP_APPLICATION_BREAK = 0xA5, - HID_USAGE_DESKTOP_APPLICATION_DEBUGGER_BREAK = 0xA6, - HID_USAGE_DESKTOP_SYSTEM_SPEAKER_MUTE = 0xA7, - HID_USAGE_DESKTOP_SYSTEM_HIBERNATE = 0xA8, - HID_USAGE_DESKTOP_SYSTEM_DISPLAY_INVERT = 0xB0, - HID_USAGE_DESKTOP_SYSTEM_DISPLAY_INTERNAL = 0xB1, - HID_USAGE_DESKTOP_SYSTEM_DISPLAY_EXTERNAL = 0xB2, - HID_USAGE_DESKTOP_SYSTEM_DISPLAY_BOTH = 0xB3, - HID_USAGE_DESKTOP_SYSTEM_DISPLAY_DUAL = 0xB4, - HID_USAGE_DESKTOP_SYSTEM_DISPLAY_TOGGLE_INT_EXT = 0xB5, - HID_USAGE_DESKTOP_SYSTEM_DISPLAY_SWAP_PRIMARY_SECONDARY = 0xB6, - HID_USAGE_DESKTOP_SYSTEM_DISPLAY_LCD_AUTOSCALE = 0xB7 + HID_USAGE_DESKTOP_POINTER = 0x01, // CP + HID_USAGE_DESKTOP_MOUSE = 0x02, // CA + // 03 Reserved + + HID_USAGE_DESKTOP_JOYSTICK = 0x04, // CA + HID_USAGE_DESKTOP_GAMEPAD = 0x05, // CA + HID_USAGE_DESKTOP_KEYBOARD = 0x06, // CA + HID_USAGE_DESKTOP_KEYPAD = 0x07, // CA + HID_USAGE_DESKTOP_MULTI_AXIS_CONTROLLER = 0x08, // CA + HID_USAGE_DESKTOP_TABLET_PC_SYSTEM = 0x09, // CA + HID_USAGE_DESKTOP_WATER_COOLING = 0x0A, // CA + HID_USAGE_DESKTOP_COMPUTER_CHASSIS = 0x0B, // CA + HID_USAGE_DESKTOP_WIRELESS_RADIO = 0x0C, // CA + HID_USAGE_DESKTOP_PORTABLE_DEVICE = 0x0D, // CA + HID_USAGE_DESKTOP_SYSTEM_MULTI_AXIS_CONTROLLER = 0x0E, // CA + HID_USAGE_DESKTOP_SPATIAL_CONTROLLER = 0x0F, // CA + HID_USAGE_DESKTOP_ASSISTIVE = 0x10, // CA + HID_USAGE_DESKTOP_DEVICE_DOCK = 0x11, // CA + HID_USAGE_DESKTOP_DOCKABLE_DEVICE = 0x12, // CA + HID_USAGE_DESKTOP_CALL_STATE_MANAGEMENT = 0x13, // CA + // 14-2F Reserved + + HID_USAGE_DESKTOP_X = 0x30, // DV + HID_USAGE_DESKTOP_Y = 0x31, // DV + HID_USAGE_DESKTOP_Z = 0x32, // DV + HID_USAGE_DESKTOP_RX = 0x33, // DV + HID_USAGE_DESKTOP_RY = 0x34, // DV + HID_USAGE_DESKTOP_RZ = 0x35, // DV + HID_USAGE_DESKTOP_SLIDER = 0x36, // DV + HID_USAGE_DESKTOP_DIAL = 0x37, // DV + HID_USAGE_DESKTOP_WHEEL = 0x38, // DV + HID_USAGE_DESKTOP_HAT_SWITCH = 0x39, // DV + HID_USAGE_DESKTOP_COUNTED_BUFFER = 0x3A, // CL + HID_USAGE_DESKTOP_BYTE_COUNT = 0x3B, // DV + HID_USAGE_DESKTOP_MOTION_WAKEUP = 0x3C, // OSC/DF + HID_USAGE_DESKTOP_START = 0x3D, // OOC + HID_USAGE_DESKTOP_SELECT = 0x3E, // OOC + // 3F Reserved + + HID_USAGE_DESKTOP_VX = 0x40, // DV + HID_USAGE_DESKTOP_VY = 0x41, // DV + HID_USAGE_DESKTOP_VZ = 0x42, // DV + HID_USAGE_DESKTOP_VBRX = 0x43, // DV + HID_USAGE_DESKTOP_VBRY = 0x44, // DV + HID_USAGE_DESKTOP_VBRZ = 0x45, // DV + HID_USAGE_DESKTOP_VNO = 0x46, // DV + HID_USAGE_DESKTOP_FEATURE_NOTIFICATION = 0x47, // DV/DF + HID_USAGE_DESKTOP_RESOLUTION_MULTIPLIER = 0x48, // DV + HID_USAGE_DESKTOP_QX = 0x49, // DV + HID_USAGE_DESKTOP_QY = 0x4A, // DV + HID_USAGE_DESKTOP_QZ = 0x4B, // DV + HID_USAGE_DESKTOP_QW = 0x4C, // DV + // 4D-7F Reserved + + HID_USAGE_DESKTOP_SYSTEM_CONTROL = 0x80, // CA + HID_USAGE_DESKTOP_SYSTEM_POWER_DOWN = 0x81, // OSC + HID_USAGE_DESKTOP_SYSTEM_SLEEP = 0x82, // OSC + HID_USAGE_DESKTOP_SYSTEM_WAKE_UP = 0x83, // OSC + HID_USAGE_DESKTOP_SYSTEM_CONTEXT_MENU = 0x84, // OSC + HID_USAGE_DESKTOP_SYSTEM_MAIN_MENU = 0x85, // OSC + HID_USAGE_DESKTOP_SYSTEM_APP_MENU = 0x86, // OSC + HID_USAGE_DESKTOP_SYSTEM_MENU_HELP = 0x87, // OSC + HID_USAGE_DESKTOP_SYSTEM_MENU_EXIT = 0x88, // OSC + HID_USAGE_DESKTOP_SYSTEM_MENU_SELECT = 0x89, // OSC + HID_USAGE_DESKTOP_SYSTEM_MENU_RIGHT = 0x8A, // RTC + HID_USAGE_DESKTOP_SYSTEM_MENU_LEFT = 0x8B, // RTC + HID_USAGE_DESKTOP_SYSTEM_MENU_UP = 0x8C, // RTC + HID_USAGE_DESKTOP_SYSTEM_MENU_DOWN = 0x8D, // RTC + HID_USAGE_DESKTOP_SYSTEM_COLD_RESTART = 0x8E, // OSC + HID_USAGE_DESKTOP_SYSTEM_WARM_RESTART = 0x8F, // OSC + HID_USAGE_DESKTOP_DPAD_UP = 0x90, // OOC + HID_USAGE_DESKTOP_DPAD_DOWN = 0x91, // OOC + HID_USAGE_DESKTOP_DPAD_RIGHT = 0x92, // OOC + HID_USAGE_DESKTOP_DPAD_LEFT = 0x93, // OOC + HID_USAGE_DESKTOP_INDEX_TRIGGER = 0x94, // MC/DV + HID_USAGE_DESKTOP_PALM_TRIGGER = 0x95, // MC/DV + HID_USAGE_DESKTOP_THUMBSTICK = 0x96, // CP + HID_USAGE_DESKTOP_SYSTEM_FUNCTION_SHIFT = 0x97, // MC + HID_USAGE_DESKTOP_SYSTEM_FUNCTION_SHIFT_LOCK = 0x98, // OOC + HID_USAGE_DESKTOP_SYSTEM_FUNCTION_SHIFT_LOCK_INDICATOR = 0x99, // DV + HID_USAGE_DESKTOP_SYSTEM_DISMISS_NOTIFICATION = 0x9A, // OSC + HID_USAGE_DESKTOP_SYSTEM_DO_NOT_DISTURB = 0x9B, // OOC + // 9C-9F Reserved + + HID_USAGE_DESKTOP_SYSTEM_DOCK = 0xA0, // OSC + HID_USAGE_DESKTOP_SYSTEM_UNDOCK = 0xA1, // OSC + HID_USAGE_DESKTOP_SYSTEM_SETUP = 0xA2, // OSC + HID_USAGE_DESKTOP_SYSTEM_BREAK = 0xA3, // OSC + HID_USAGE_DESKTOP_SYSTEM_DEBUGGER_BREAK = 0xA4, // OSC + HID_USAGE_DESKTOP_APPLICATION_BREAK = 0xA5, // OSC + HID_USAGE_DESKTOP_APPLICATION_DEBUGGER_BREAK = 0xA6, // OSC + HID_USAGE_DESKTOP_SYSTEM_SPEAKER_MUTE = 0xA7, // OSC + HID_USAGE_DESKTOP_SYSTEM_HIBERNATE = 0xA8, // OSC + HID_USAGE_DESKTOP_SYSTEM_MICROPHONE_MUTE = 0xA9, // OOC + HID_USAGE_DESKTOP_SYSTEM_ACCESSIBILITY_BINDING = 0xAA, // OOC + // AB-AF Reserved + + HID_USAGE_DESKTOP_SYSTEM_DISPLAY_INVERT = 0xB0, // OSC + HID_USAGE_DESKTOP_SYSTEM_DISPLAY_INTERNAL = 0xB1, // OSC + HID_USAGE_DESKTOP_SYSTEM_DISPLAY_EXTERNAL = 0xB2, // OSC + HID_USAGE_DESKTOP_SYSTEM_DISPLAY_BOTH = 0xB3, // OSC + HID_USAGE_DESKTOP_SYSTEM_DISPLAY_DUAL = 0xB4, // OSC + HID_USAGE_DESKTOP_SYSTEM_DISPLAY_TOGGLE_INT_EXT = 0xB5, // OSC + HID_USAGE_DESKTOP_SYSTEM_DISPLAY_SWAP_PRIMARY_SECONDARY = 0xB6, // OSC + HID_USAGE_DESKTOP_SYSTEM_DISPLAY_LCD_AUTOSCALE = 0xB7, // OSC + // B8-BF Reserved + + HID_USAGE_DESKTOP_SENSOR_ZONE = 0xC0, // CL + HID_USAGE_DESKTOP_RPM = 0xC1, // DV + HID_USAGE_DESKTOP_COOLANT_LEVEL = 0xC2, // DV + HID_USAGE_DESKTOP_COOLANT_CRITICAL_LEVEL = 0xC3, // SV + HID_USAGE_DESKTOP_COOLANT_PUMP = 0xC4, // US + HID_USAGE_DESKTOP_CHASSIS_ENCLOSURE = 0xC5, // CL + HID_USAGE_DESKTOP_WIRELESS_RADIO_BUTTON = 0xC6, // OOC + HID_USAGE_DESKTOP_WIRELESS_RADIO_LED = 0xC7, // OOC + HID_USAGE_DESKTOP_WIRELESS_RADIO_SLIDER_SWITCH = 0xC8, // OOC + HID_USAGE_DESKTOP_SYSTEM_DISPLAY_ROTATION_LOCK_BUTTON = 0xC9, // OOC + HID_USAGE_DESKTOP_SYSTEM_DISPLAY_ROTATION_LOCK_SLIDER_SWITCH = 0xCA, // OOC + HID_USAGE_DESKTOP_CONTROL_ENABLE = 0xCB, // DF + // CC-CF Reserved + + HID_USAGE_DESKTOP_DOCKABLE_DEVICE_UNIQUE_ID = 0xD0, // DV + HID_USAGE_DESKTOP_DOCKABLE_DEVICE_VENDOR_ID = 0xD1, // DV + HID_USAGE_DESKTOP_DOCKABLE_DEVICE_PRIMARY_USAGE_PAGE = 0xD2, // DV + HID_USAGE_DESKTOP_DOCKABLE_DEVICE_PRIMARY_USAGE_ID = 0xD3, // DV + HID_USAGE_DESKTOP_DOCKABLE_DEVICE_DOCKING_STATE = 0xD4, // DF + HID_USAGE_DESKTOP_DOCKABLE_DEVICE_DISPLAY_OCCULSION = 0xD5, // CL + HID_USAGE_DESKTOP_DOCKABLE_DEVICE_OBJECT_TYPE = 0xD6, // DV + // D7-DF Reserved + + HID_USAGE_DESKTOP_CALL_ACTIVE_LED = 0xE0, // OOC + HID_USAGE_DESKTOP_CALL_MUTE_TOGGLE = 0xE1, // OSC + HID_USAGE_DESKTOP_CALL_MUTE_LED = 0xE2 // OOC + // E3-FFFF Reserved +}; + +/// HID Usage Table: Simulation Controls Page (0x02) +enum { + HID_USAGE_SIMULATION_CONTROLS_FLIGHT_SIMULATION_DEVICE = 0x01, // CA + HID_USAGE_SIMULATION_CONTROLS_AUTOMOBILE_SIMULATION_DEVICE = 0x02, // CA + HID_USAGE_SIMULATION_CONTROLS_TANK_SIMULATION_DEVICE = 0x03, // CA + HID_USAGE_SIMULATION_CONTROLS_SPACESHIP_SIMULATION_DEVICE = 0x04, // CA + HID_USAGE_SIMULATION_CONTROLS_SUBMARINE_SIMULATION_DEVICE = 0x05, // CA + HID_USAGE_SIMULATION_CONTROLS_SAILING_SIMULATION_DEVICE = 0x06, // CA + HID_USAGE_SIMULATION_CONTROLS_MOTORCYCLE_SIMULATION_DEVICE = 0x07, // CA + HID_USAGE_SIMULATION_CONTROLS_SPORTS_SIMULATION_DEVICE = 0x08, // CA + HID_USAGE_SIMULATION_CONTROLS_AIRPLANE_SIMULATION_DEVICE = 0x09, // CA + HID_USAGE_SIMULATION_CONTROLS_HELICOPTER_SIMULATION_DEVICE = 0x0A, // CA + HID_USAGE_SIMULATION_CONTROLS_MAGIC_CARPET_SIMULATION_DEVICE = 0x0B, // CA + HID_USAGE_SIMULATION_CONTROLS_BICYCLE_SIMULATION_DEVICE = 0x0C, // CA + // 0D-1F Reserved + + HID_USAGE_SIMULATION_CONTROLS_FLIGHT_CONTROL_STICK = 0x20, // CA + HID_USAGE_SIMULATION_CONTROLS_FLIGHT_STICK = 0x21, // CA + HID_USAGE_SIMULATION_CONTROLS_CYCLIC_CONTROL = 0x22, // CP + HID_USAGE_SIMULATION_CONTROLS_CYCLIC_TRIM = 0x23, // CP + HID_USAGE_SIMULATION_CONTROLS_FLIGHT_YOKE = 0x24, // CA + HID_USAGE_SIMULATION_CONTROLS_TRACK_CONTROL = 0x25, // CP + // 26-AF Reserved + + HID_USAGE_SIMULATION_CONTROLS_AILERON = 0xB0, // DV + HID_USAGE_SIMULATION_CONTROLS_AILERON_TRIM = 0xB1, // DV + HID_USAGE_SIMULATION_CONTROLS_ANTI_TORQUE_CONTROL = 0xB2, // DV + HID_USAGE_SIMULATION_CONTROLS_AUTOPILOT_ENABLE = 0xB3, // OOC + HID_USAGE_SIMULATION_CONTROLS_CHAFF_RELEASE = 0xB4, // OSC + HID_USAGE_SIMULATION_CONTROLS_COLLECTIVE_CONTROL = 0xB5, // DV + HID_USAGE_SIMULATION_CONTROLS_DIVE_BRAKE = 0xB6, // DV + HID_USAGE_SIMULATION_CONTROLS_ELECTRONIC_COUNTERMEASURES = 0xB7, // OOC + HID_USAGE_SIMULATION_CONTROLS_ELEVATOR = 0xB8, // DV + HID_USAGE_SIMULATION_CONTROLS_ELEVATOR_TRIM = 0xB9, // DV + HID_USAGE_SIMULATION_CONTROLS_RUDDER = 0xBA, // DV + HID_USAGE_SIMULATION_CONTROLS_THROTTLE = 0xBB, // DV + HID_USAGE_SIMULATION_CONTROLS_FLIGHT_COMMUNICATIONS = 0xBC, // OOC + HID_USAGE_SIMULATION_CONTROLS_FLARE_RELEASE = 0xBD, // OSC + HID_USAGE_SIMULATION_CONTROLS_LANDING_GEAR = 0xBE, // OOC + HID_USAGE_SIMULATION_CONTROLS_TOE_BRAKE = 0xBF, // DV + HID_USAGE_SIMULATION_CONTROLS_TRIGGER = 0xC0, // MC + HID_USAGE_SIMULATION_CONTROLS_WEAPONS_ARM = 0xC1, // OOC + HID_USAGE_SIMULATION_CONTROLS_WEAPONS_SELECT = 0xC2, // OSC + HID_USAGE_SIMULATION_CONTROLS_WING_FLAPS = 0xC3, // DV + HID_USAGE_SIMULATION_CONTROLS_ACCELERATOR = 0xC4, // DV + HID_USAGE_SIMULATION_CONTROLS_BRAKE = 0xC5, // DV + HID_USAGE_SIMULATION_CONTROLS_CLUTCH = 0xC6, // DV + HID_USAGE_SIMULATION_CONTROLS_SHIFTER = 0xC7, // DV + HID_USAGE_SIMULATION_CONTROLS_STEERING = 0xC8, // DV + HID_USAGE_SIMULATION_CONTROLS_TURRET_DIRECTION = 0xC9, // DV + HID_USAGE_SIMULATION_CONTROLS_BARREL_ELEVATION = 0xCA, // DV + HID_USAGE_SIMULATION_CONTROLS_DIVE_PLANE = 0xCB, // DV + HID_USAGE_SIMULATION_CONTROLS_BALLAST = 0xCC, // DV + HID_USAGE_SIMULATION_CONTROLS_BICYCLE_CRANK = 0xCD, // DV + HID_USAGE_SIMULATION_CONTROLS_HANDLE_BARS = 0xCE, // DV + HID_USAGE_SIMULATION_CONTROLS_FRONT_BRAKE = 0xCF, // DV + HID_USAGE_SIMULATION_CONTROLS_REAR_BRAKE = 0xD0, // DV + // D1-FFFF Reserved +}; + +/// HID Usage Table: VR Controls Page (0x03) +enum { + HID_USAGE_VR_CONTROLS_BELT = 0x01, // CA + HID_USAGE_VR_CONTROLS_BODY_SUIT = 0x02, // CA + HID_USAGE_VR_CONTROLS_FLEXOR = 0x03, // CP + HID_USAGE_VR_CONTROLS_GLOVE = 0x04, // CA + HID_USAGE_VR_CONTROLS_HEAD_TRACKER = 0x05, // CP + HID_USAGE_VR_CONTROLS_HEAD_MOUNTED_DISPLAY = 0x06, // CA + HID_USAGE_VR_CONTROLS_HAND_TRACKER = 0x07, // CA + HID_USAGE_VR_CONTROLS_OCULOMETER = 0x08, // CA + HID_USAGE_VR_CONTROLS_VEST = 0x09, // CA + HID_USAGE_VR_CONTROLS_ANIMATRONIC_DEVICE = 0x0A, // CA + // 0B-1F Reserved + + HID_USAGE_VR_CONTROLS_STEREO_ENABLE = 0x20, // OOC + HID_USAGE_VR_CONTROLS_DISPLAY_ENABLE = 0x21 // OOC + // 22-FFFF Reserved +}; + +/// HID Usage Table: Sports Controls Page (0x04) +enum { + HID_USAGE_SPORTS_CONTROLS_BASEBALL_BAT = 0x01, // CA + HID_USAGE_SPORTS_CONTROLS_GOLF_CLUB = 0x02, // CA + HID_USAGE_SPORTS_CONTROLS_ROWING_MACHINE = 0x03, // CA + HID_USAGE_SPORTS_CONTROLS_TREADMILL = 0x04, // CA + // 05-2F Reserved + + HID_USAGE_SPORTS_CONTROLS_OAR = 0x30, // DV + HID_USAGE_SPORTS_CONTROLS_SLOPE = 0x31, // DV + HID_USAGE_SPORTS_CONTROLS_RATE = 0x32, // DV + HID_USAGE_SPORTS_CONTROLS_STICK_SPEED = 0x33, // DV + HID_USAGE_SPORTS_CONTROLS_STICK_FACE_ANGLE = 0x34, // DV + HID_USAGE_SPORTS_CONTROLS_STICK_HEEL_TOE = 0x35, // DV + HID_USAGE_SPORTS_CONTROLS_STICK_FOLLOW_THROUGH = 0x36, // DV + HID_USAGE_SPORTS_CONTROLS_STICK_TEMPO = 0x37, // DV + HID_USAGE_SPORTS_CONTROLS_STICK_TYPE = 0x38, // NAry + HID_USAGE_SPORTS_CONTROLS_STICK_HEIGHT = 0x39, // DV + // 3A-4F Reserved + + HID_USAGE_SPORTS_CONTROLS_PUTTER = 0x50, // Sel + HID_USAGE_SPORTS_CONTROLS_1_IRON = 0x51, // Sel + HID_USAGE_SPORTS_CONTROLS_2_IRON = 0x52, // Sel + HID_USAGE_SPORTS_CONTROLS_3_IRON = 0x53, // Sel + HID_USAGE_SPORTS_CONTROLS_4_IRON = 0x54, // Sel + HID_USAGE_SPORTS_CONTROLS_5_IRON = 0x55, // Sel + HID_USAGE_SPORTS_CONTROLS_6_IRON = 0x56, // Sel + HID_USAGE_SPORTS_CONTROLS_7_IRON = 0x57, // Sel + HID_USAGE_SPORTS_CONTROLS_8_IRON = 0x58, // Sel + HID_USAGE_SPORTS_CONTROLS_9_IRON = 0x59, // Sel + HID_USAGE_SPORTS_CONTROLS_10_IRON = 0x5A, // Sel + HID_USAGE_SPORTS_CONTROLS_11_IRON = 0x5B, // Sel + HID_USAGE_SPORTS_CONTROLS_SAND_WEDGE = 0x5C, // Sel + HID_USAGE_SPORTS_CONTROLS_LOFT_WEDGE = 0x5D, // Sel + HID_USAGE_SPORTS_CONTROLS_POWER_WEDGE = 0x5E, // Sel + HID_USAGE_SPORTS_CONTROLS_1_WOOD = 0x5F, // Sel + HID_USAGE_SPORTS_CONTROLS_3_WOOD = 0x60, // Sel + HID_USAGE_SPORTS_CONTROLS_5_WOOD = 0x61, // Sel + HID_USAGE_SPORTS_CONTROLS_7_WOOD = 0x62, // Sel + HID_USAGE_SPORTS_CONTROLS_9_WOOD = 0x63, // Sel + // 64-FFFF Reserved +}; + +/// HID Usage Table: Game Controls Page (0x05) +enum { + HID_USAGE_GAME_CONTROLS_3D_GAME_CONTROLLER = 0x01, // CA + HID_USAGE_GAME_CONTROLS_PINBALL_DEVICE = 0x02, // CA + HID_USAGE_GAME_CONTROLS_GUN_DEVICE = 0x03, // CA + // 04-1F Reserved + + HID_USAGE_GAME_CONTROLS_POINT_OF_VIEW = 0x20, // CP + HID_USAGE_GAME_CONTROLS_TURN_RIGHT_LEFT = 0x21, // DV + HID_USAGE_GAME_CONTROLS_PITCH_FORWARD_BACKWARD = 0x22, // DV + HID_USAGE_GAME_CONTROLS_ROLL_RIGHT_LEFT = 0x23, // DV + HID_USAGE_GAME_CONTROLS_MOVE_RIGHT_LEFT = 0x24, // DV + HID_USAGE_GAME_CONTROLS_MOVE_FORWARD_BACKWARD = 0x25, // DV + HID_USAGE_GAME_CONTROLS_MOVE_UP_DOWN = 0x26, // DV + HID_USAGE_GAME_CONTROLS_LEAN_RIGHT_LEFT = 0x27, // DV + HID_USAGE_GAME_CONTROLS_LEAN_FORWARD_BACKWARD = 0x28, // DV + HID_USAGE_GAME_CONTROLS_HEIGHT_OF_POV = 0x29, // DV + HID_USAGE_GAME_CONTROLS_FLIPPER = 0x2A, // MC + HID_USAGE_GAME_CONTROLS_SECONDARY_FLIPPER = 0x2B, // MC + HID_USAGE_GAME_CONTROLS_BUMP = 0x2C, // MC + HID_USAGE_GAME_CONTROLS_NEW_GAME = 0x2D, // OSC + HID_USAGE_GAME_CONTROLS_SHOOT_BALL = 0x2E, // OSC + HID_USAGE_GAME_CONTROLS_PLAYER = 0x2F, // OSC + HID_USAGE_GAME_CONTROLS_GUN_BOLT = 0x30, // OOC + HID_USAGE_GAME_CONTROLS_GUN_CLIP = 0x31, // OOC + HID_USAGE_GAME_CONTROLS_GUN_SELECTOR = 0x32, // NAry + HID_USAGE_GAME_CONTROLS_GUN_SINGLE_SHOT = 0x33, // Sel + HID_USAGE_GAME_CONTROLS_GUN_BURST = 0x34, // Sel + HID_USAGE_GAME_CONTROLS_GUN_AUTOMATIC = 0x35, // Sel + HID_USAGE_GAME_CONTROLS_GUN_SAFETY = 0x36, // OOC + HID_USAGE_GAME_CONTROLS_GAMEPAD_FIRE_JUMP = 0x37, // CL + // 38 Reserved + + HID_USAGE_GAME_CONTROLS_GAMEPAD_TRIGGER = 0x39, // CL + HID_USAGE_GAME_CONTROLS_FORM_FITTING_GAMEPAD = 0x3A // SF + // 3B-FFFF Reserved +}; + +/// HID Usage Table: Generic Device Controls Page (0x06) +enum { + HID_USAGE_GENERIC_DEVICE_CONTROLS_BACKGROUND_NONUSER_CONTROLS = 0x01, // CA + // 02-1F Reserved + + HID_USAGE_GENERIC_DEVICE_CONTROLS_BATTERY_STRENGTH = 0x20, // DV + HID_USAGE_GENERIC_DEVICE_CONTROLS_WIRELESS_CHANNEL = 0x21, // DV + HID_USAGE_GENERIC_DEVICE_CONTROLS_WIRELESS_ID = 0x22, // DV + HID_USAGE_GENERIC_DEVICE_CONTROLS_DISCOVER_WIRELESS_CONTROL = 0x23, // OSC + HID_USAGE_GENERIC_DEVICE_CONTROLS_SECURITY_CODE_CHARACTER_ENTERED = 0x24, // OSC + HID_USAGE_GENERIC_DEVICE_CONTROLS_SECURITY_CODE_CHARACTER_ERASED = 0x25, // OSC + HID_USAGE_GENERIC_DEVICE_CONTROLS_SECURITY_CODE_CLEARED = 0x26, // OSC + HID_USAGE_GENERIC_DEVICE_CONTROLS_SEQUENCE_ID = 0x27, // DV + HID_USAGE_GENERIC_DEVICE_CONTROLS_SEQUENCE_ID_RESET = 0x28, // DF + HID_USAGE_GENERIC_DEVICE_CONTROLS_RF_SIGNAL_STRENGTH = 0x29, // DV + HID_USAGE_GENERIC_DEVICE_CONTROLS_SOFTWARE_VERSION = 0x2A, // CL + HID_USAGE_GENERIC_DEVICE_CONTROLS_PROTOCOL_VERSION = 0x2B, // CL + HID_USAGE_GENERIC_DEVICE_CONTROLS_HARDWARE_VERSION = 0x2C, // CL + HID_USAGE_GENERIC_DEVICE_CONTROLS_MAJOR = 0x2D, // SV + HID_USAGE_GENERIC_DEVICE_CONTROLS_MINOR = 0x2E, // SV + HID_USAGE_GENERIC_DEVICE_CONTROLS_REVISION = 0x2F, // SV + HID_USAGE_GENERIC_DEVICE_CONTROLS_HANDEDNESS = 0x30, // NAry + HID_USAGE_GENERIC_DEVICE_CONTROLS_EITHER_HAND = 0x31, // Sel + HID_USAGE_GENERIC_DEVICE_CONTROLS_LEFT_HAND = 0x32, // Sel + HID_USAGE_GENERIC_DEVICE_CONTROLS_RIGHT_HAND = 0x33, // Sel + HID_USAGE_GENERIC_DEVICE_CONTROLS_BOTH_HANDS = 0x34, // Sel + // 35-3F Reserved + + HID_USAGE_GENERIC_DEVICE_CONTROLS_GRIP_POSE_OFFSET = 0x40, // CP + HID_USAGE_GENERIC_DEVICE_CONTROLS_POINTER_POSE_OFFSET = 0x41 // CP + // 42-FFFF Reserved +}; + +/// HID Usage Table: Keyboard/Keypad Page (0x07) +/// Defined above + +/// HID Usage Table: LED Page (0x08) +enum { + HID_USAGE_LED_NUM_LOCK = 0x01, // OOC + HID_USAGE_LED_CAPS_LOCK = 0x02, // OOC + HID_USAGE_LED_SCROLL_LOCK = 0x03, // OOC + HID_USAGE_LED_COMPOSE = 0x04, // OOC + HID_USAGE_LED_KANA = 0x05, // OOC + HID_USAGE_LED_POWER = 0x06, // OOC + HID_USAGE_LED_SHIFT = 0x07, // OOC + HID_USAGE_LED_DO_NOT_SHIFT = 0x08, // OOC + HID_USAGE_LED_MUTE = 0x09, // OOC + HID_USAGE_LED_TONE_ENABLE = 0x0A, // OOC + HID_USAGE_LED_HIGH_CUT_FILTER = 0x0B, // OOC + HID_USAGE_LED_LOW_CUT_FILTER = 0x0C, // OOC + HID_USAGE_LED_EQUALIZER_ENABLE = 0x0D, // OOC + HID_USAGE_LED_SOUND_FIELD_ON = 0x0E, // OOC + HID_USAGE_LED_SURROUND_ON = 0x0F, // OOC + HID_USAGE_LED_REPEAT = 0x10, // OOC + HID_USAGE_LED_STEREO = 0x11, // OOC + HID_USAGE_LED_SAMPLING_RATE_DETECT = 0x12, // OOC + HID_USAGE_LED_SPINNING = 0x13, // OOC + HID_USAGE_LED_CAV = 0x14, // OOC + HID_USAGE_LED_CLV = 0x15, // OOC + HID_USAGE_LED_RECORDING_FORMAT_DETECT = 0x16, // OOC + HID_USAGE_LED_OFF_HOOK = 0x17, // OOC + HID_USAGE_LED_RING = 0x18, // OOC + HID_USAGE_LED_MESSAGE_WAITING = 0x19, // OOC + HID_USAGE_LED_DATA_MODE = 0x1A, // OOC + HID_USAGE_LED_BATTERY_OPERATION = 0x1B, // OOC + HID_USAGE_LED_BATTERY_OK = 0x1C, // OOC + HID_USAGE_LED_BATTERY_LOW = 0x1D, // OOC + HID_USAGE_LED_SPEAKER = 0x1E, // OOC + HID_USAGE_LED_HEADSET = 0x1F, // OOC + HID_USAGE_LED_HOLD = 0x20, // OOC + HID_USAGE_LED_MICROPHONE = 0x21, // OOC + HID_USAGE_LED_COVERAGE = 0x22, // OOC + HID_USAGE_LED_NIGHT_MODE = 0x23, // OOC + HID_USAGE_LED_SEND_CALLS = 0x24, // OOC + HID_USAGE_LED_CALL_PICKUPS = 0x25, // OOC + HID_USAGE_LED_CONFERENCE = 0x26, // OOC + HID_USAGE_LED_STANDBY = 0x27, // OOC + HID_USAGE_LED_CAMERA_ON = 0x28, // OOC + HID_USAGE_LED_CAMERA_OFF = 0x29, // OOC + HID_USAGE_LED_ON_LINE = 0x2A, // OOC + HID_USAGE_LED_OFF_LINE = 0x2B, // OOC + HID_USAGE_LED_BUSY = 0x2C, // OOC + HID_USAGE_LED_READY = 0x2D, // OOC + HID_USAGE_LED_PAPER_OUT = 0x2E, // OOC + HID_USAGE_LED_PAPER_JAM = 0x2F, // OOC + HID_USAGE_LED_REMOTE = 0x30, // OOC + HID_USAGE_LED_FORWARD = 0x31, // OOC + HID_USAGE_LED_REVERSE = 0x32, // OOC + HID_USAGE_LED_STOP = 0x33, // OOC + HID_USAGE_LED_REWIND = 0x34, // OOC + HID_USAGE_LED_FAST_FORWARD = 0x35, // OOC + HID_USAGE_LED_PLAY = 0x36, // OOC + HID_USAGE_LED_PAUSE = 0x37, // OOC + HID_USAGE_LED_RECORD = 0x38, // OOC + HID_USAGE_LED_ERROR = 0x39, // OOC + HID_USAGE_LED_USAGE_SELECTED_INDICATOR = 0x3A, // US + HID_USAGE_LED_USAGE_IN_USE_INDICATOR = 0x3B, // US + HID_USAGE_LED_USAGE_MULTI_MODE_INDICATOR = 0x3C, // UM + HID_USAGE_LED_INDICATOR_ON = 0x3D, // Sel + HID_USAGE_LED_INDICATOR_FLASH = 0x3E, // Sel + HID_USAGE_LED_INDICATOR_SLOW_BLINK = 0x3F, // Sel + HID_USAGE_LED_INDICATOR_FAST_BLINK = 0x40, // Sel + HID_USAGE_LED_INDICATOR_OFF = 0x41, // Sel + HID_USAGE_LED_FLASH_ON_TIME = 0x42, // DV + HID_USAGE_LED_SLOW_BLINK_ON_TIME = 0x43, // DV + HID_USAGE_LED_SLOW_BLINK_OFF_TIME = 0x44, // DV + HID_USAGE_LED_FAST_BLINK_ON_TIME = 0x45, // DV + HID_USAGE_LED_FAST_BLINK_OFF_TIME = 0x46, // DV + HID_USAGE_LED_USAGE_INDICATOR_COLOR = 0x47, // UM + HID_USAGE_LED_INDICATOR_RED = 0x48, // Sel + HID_USAGE_LED_INDICATOR_GREEN = 0x49, // Sel + HID_USAGE_LED_INDICATOR_AMBER = 0x4A, // Sel + HID_USAGE_LED_GENERIC_INDICATOR = 0x4B, // OOC + HID_USAGE_LED_SYSTEM_SUSPEND = 0x4C, // OOC + HID_USAGE_LED_EXTERNAL_POWER_CONNECTED = 0x4D, // OOC + HID_USAGE_LED_INDICATOR_BLUE = 0x4E, // Sel + HID_USAGE_LED_INDICATOR_ORANGE = 0x4F, // Sel + HID_USAGE_LED_GOOD_STATUS = 0x50, // OOC + HID_USAGE_LED_WARNING_STATUS = 0x51, // OOC + HID_USAGE_LED_RGB_LED = 0x52, // CL + HID_USAGE_LED_RED_LED_CHANNEL = 0x53, // DV + HID_USAGE_LED_BLUE_LED_CHANNEL = 0x54, // DV + HID_USAGE_LED_GREEN_LED_CHANNEL = 0x55, // DV + HID_USAGE_LED_LED_INTENSITY = 0x56, // DV + HID_USAGE_LED_SYSTEM_MICROPHONE_MUTE = 0x57, // OOC + // 58-5F Reserved + + HID_USAGE_LED_PLAYER_INDICATOR = 0x60, // NAry + HID_USAGE_LED_PLAYER_1 = 0x61, // Sel + HID_USAGE_LED_PLAYER_2 = 0x62, // Sel + HID_USAGE_LED_PLAYER_3 = 0x63, // Sel + HID_USAGE_LED_PLAYER_4 = 0x64, // Sel + HID_USAGE_LED_PLAYER_5 = 0x65, // Sel + HID_USAGE_LED_PLAYER_6 = 0x66, // Sel + HID_USAGE_LED_PLAYER_7 = 0x67, // Sel + HID_USAGE_LED_PLAYER_8 = 0x68, // Sel + // 69-FFFF Reserved +}; + +/// HID Usage Table: Button Page (0x09) +/// Intentionally skipped + +/// HID Usage Table: Ordinal Page (0x0A) +/// Intentionally skipped + +/// HID Usage Table: Telephony Device Page (0x0B) +enum { + HID_USAGE_TELEPHONY_PHONE = 0x0001, // CA + HID_USAGE_TELEPHONY_ANSWERING_MACHINE = 0x0002, // CA + HID_USAGE_TELEPHONY_MESSAGE_CONTROLS = 0x0003, // CL + HID_USAGE_TELEPHONY_HANDSET = 0x0004, // CL + HID_USAGE_TELEPHONY_HEADSET = 0x0005, // CL/CA + HID_USAGE_TELEPHONY_TELEPHONY_KEY_PAD = 0x0006, // NAry + HID_USAGE_TELEPHONY_PROGRAMMABLE_BUTTON = 0x0007, // NAry + // 08-1F Reserved + + HID_USAGE_TELEPHONY_HOOK_SWITCH = 0x0020, // OOC + HID_USAGE_TELEPHONY_FLASH = 0x0021, // MC + HID_USAGE_TELEPHONY_FEATURE = 0x0022, // OSC + HID_USAGE_TELEPHONY_HOLD = 0x0023, // OOC + HID_USAGE_TELEPHONY_REDIAL = 0x0024, // OSC + HID_USAGE_TELEPHONY_TRANSFER = 0x0025, // OSC + HID_USAGE_TELEPHONY_DROP = 0x0026, // OSC + HID_USAGE_TELEPHONY_PARK = 0x0027, // OOC + HID_USAGE_TELEPHONY_FORWARD_CALLS = 0x0028, // OOC + HID_USAGE_TELEPHONY_ALTERNATE_FUNCTION = 0x0029, // MC + HID_USAGE_TELEPHONY_LINE = 0x002A, // OSC/NAry + HID_USAGE_TELEPHONY_SPEAKER_PHONE = 0x002B, // OOC + HID_USAGE_TELEPHONY_CONFERENCE = 0x002C, // OOC + HID_USAGE_TELEPHONY_RING_ENABLE = 0x002D, // OOC + HID_USAGE_TELEPHONY_RING_SELECT = 0x002E, // OSC + HID_USAGE_TELEPHONY_PHONE_MUTE = 0x002F, // OOC + HID_USAGE_TELEPHONY_CALLER_ID = 0x0030, // MC + HID_USAGE_TELEPHONY_SEND = 0x0031, // OOC + // 32-4F Reserved + + HID_USAGE_TELEPHONY_SPEED_DIAL = 0x0050, // OSC + HID_USAGE_TELEPHONY_STORE_NUMBER = 0x0051, // OSC + HID_USAGE_TELEPHONY_RECALL_NUMBER = 0x0052, // OSC + HID_USAGE_TELEPHONY_PHONE_DIRECTORY = 0x0053, // OOC + // 54-6F Reserved + + HID_USAGE_TELEPHONY_VOICE_MAIL = 0x0070, // OOC + HID_USAGE_TELEPHONY_SCREEN_CALLS = 0x0071, // OOC + HID_USAGE_TELEPHONY_DO_NOT_DISTURB = 0x0072, // OOC + HID_USAGE_TELEPHONY_MESSAGE = 0x0073, // OSC + HID_USAGE_TELEPHONY_ANSWER_ON_OFF = 0x0074, // OOC + // 75-8F Reserved + + HID_USAGE_TELEPHONY_INSIDE_DIAL_TONE = 0x0090, // MC + HID_USAGE_TELEPHONY_OUTSIDE_DIAL_TONE = 0x0091, // MC + HID_USAGE_TELEPHONY_INSIDE_RING_TONE = 0x0092, // MC + HID_USAGE_TELEPHONY_OUTSIDE_RING_TONE = 0x0093, // MC + HID_USAGE_TELEPHONY_PRIORITY_RING_TONE = 0x0094, // MC + HID_USAGE_TELEPHONY_INSIDE_RINGBACK = 0x0095, // MC + HID_USAGE_TELEPHONY_PRIORITY_RINGBACK = 0x0096, // MC + HID_USAGE_TELEPHONY_LINE_BUSY_TONE = 0x0097, // MC + HID_USAGE_TELEPHONY_REORDER_TONE = 0x0098, // MC + HID_USAGE_TELEPHONY_CALL_WAITING_TONE = 0x0099, // MC + HID_USAGE_TELEPHONY_CONFIRMATION_TONE_1 = 0x009A, // MC + HID_USAGE_TELEPHONY_CONFIRMATION_TONE_2 = 0x009B, // MC + HID_USAGE_TELEPHONY_TONES_OFF = 0x009C, // OOC + HID_USAGE_TELEPHONY_OUTSIDE_RINGBACK = 0x009D, // MC + HID_USAGE_TELEPHONY_RINGER = 0x009E, // OOC + // 9F-AF Reserved + + HID_USAGE_TELEPHONY_PHONE_KEY_0 = 0x00B0, // Sel + HID_USAGE_TELEPHONY_PHONE_KEY_1 = 0x00B1, // Sel + HID_USAGE_TELEPHONY_PHONE_KEY_2 = 0x00B2, // Sel + HID_USAGE_TELEPHONY_PHONE_KEY_3 = 0x00B3, // Sel + HID_USAGE_TELEPHONY_PHONE_KEY_4 = 0x00B4, // Sel + HID_USAGE_TELEPHONY_PHONE_KEY_5 = 0x00B5, // Sel + HID_USAGE_TELEPHONY_PHONE_KEY_6 = 0x00B6, // Sel + HID_USAGE_TELEPHONY_PHONE_KEY_7 = 0x00B7, // Sel + HID_USAGE_TELEPHONY_PHONE_KEY_8 = 0x00B8, // Sel + HID_USAGE_TELEPHONY_PHONE_KEY_9 = 0x00B9, // Sel + HID_USAGE_TELEPHONY_PHONE_KEY_STAR = 0x00BA, // Sel + HID_USAGE_TELEPHONY_PHONE_KEY_POUND = 0x00BB, // Sel + HID_USAGE_TELEPHONY_PHONE_KEY_A = 0x00BC, // Sel + HID_USAGE_TELEPHONY_PHONE_KEY_B = 0x00BD, // Sel + HID_USAGE_TELEPHONY_PHONE_KEY_C = 0x00BE, // Sel + HID_USAGE_TELEPHONY_PHONE_KEY_D = 0x00BF, // Sel + HID_USAGE_TELEPHONY_PHONE_CALL_HISTORY_KEY = 0x00C0, // Sel + HID_USAGE_TELEPHONY_PHONE_CALLER_ID_KEY = 0x00C1, // Sel + HID_USAGE_TELEPHONY_PHONE_SETINGS_KEY = 0x00C2, // Sel + // C3-EF Reserved + + HID_USAGE_TELEPHONY_HOST_CONTROL = 0x00F0, // OOC + HID_USAGE_TELEPHONY_HOST_AVAILABLE = 0x00F1, // OOC + HID_USAGE_TELEPHONY_HOST_CALL_ACTIVE = 0x00F2, // OOC + HID_USAGE_TELEPHONY_ACTIVATE_HANDSET_AUDIO = 0x00F3, // OOC + HID_USAGE_TELEPHONY_RING_TYPE = 0x00F4, // NAry + HID_USAGE_TELEPHONY_REDIABLE_PHONE_NUMBER = 0x00F5, // OOC + // F6-F7 Reserved + + HID_USAGE_TELEPHONY_STOP_RING_TONE = 0x00F8, // Sel + HID_USAGE_TELEHONY_PSTN_RING_TONE = 0x00F9, // Sel + HID_USAGE_TELEPHONY_HOST_RING_TONE = 0x00FA, // Sel + HID_USAGE_TELEPHONY_ALERT_SOUND_ERROR = 0x00FB, // Sel + HID_USAGE_TELEPHONY_ALERT_SOUND_CONFIRM = 0x00FC, // Sel + HID_USAGE_TELEPHONY_ALERT_SOUND_NOTIFICATION = 0x00FD, // Sel + HID_USAGE_TELEPHONY_SILENT_RING = 0x00FE, // Sel + // FF-107 Reserved + + HID_USAGE_TELEPHONY_EMAIL_MESSAGE_WAITING = 0x0108, // OOC + HID_USAGE_TELEPHONY_VOICEMAIL_MESSAGE_WAITING = 0x0109, // OOC + HID_USAGE_TELEPHONY_HOST_HOLD = 0x010A, // OOC + // 10B-10F Reserved + + HID_USAGE_TELEPHONY_INCOMING_CALL_HISTORY_COUNT = 0x0110, // DV + HID_USAGE_TELEPHONY_OUTGOING_CALL_HISTORY_COUNT = 0x0111, // DV + HID_USAGE_TELEPHONY_INCOMING_CALL_HISTORY = 0x0112, // CL + HID_USAGE_TELEPHONY_OUTGOING_CALL_HISTORY = 0x0113, // CL + HID_USAGE_TELEPHONY_PHONE_LOCALE = 0x0114, // DV + // 115-13F Reserved + + HID_USAGE_TELEPHONY_PHONE_TIME_SECOND = 0x0140, // DV + HID_USAGE_TELEPHONY_PHONE_TIME_MINUTE = 0x0141, // DV + HID_USAGE_TELEPHONY_PHONE_TIME_HOUR = 0x0142, // DV + HID_USAGE_TELEPHONY_PHONE_DATE_DAY = 0x0143, // DV + HID_USAGE_TELEPHONY_PHONE_DATE_MONTH = 0x0144, // DV + HID_USAGE_TELEPHONY_PHONE_DATE_YEAR = 0x0145, // DV + HID_USAGE_TELEPHONY_HANDSET_NICKNAME = 0x0146, // DV + HID_USAGE_TELEPHONY_ADDRESS_BOOK_ID = 0x0147, // DV + // 148-149 Reserved + + HID_USAGE_TELEPHONY_CALL_DURATION = 0x014A, // DV + HID_USAGE_TELEPHONY_DUAL_MODE_PHONE = 0x014B, // CA + // 14C-FFFF Reserved }; /// HID Usage Table: Consumer Page (0x0C) -/// Only contains controls that supported by Windows (whole list is too long) enum { - HID_USAGE_CONSUMER_UNASSIGNED = 0x0000, + HID_USAGE_CONSUMER_UNASSIGNED = 0x0000, // Generic Control - HID_USAGE_CONSUMER_CONTROL = 0x0001, - HID_USAGE_CONSUMER_NUMERIC_KEY_PAD = 0x0002, - HID_USAGE_CONSUMER_PROGRAMMABLE_BUTTONS = 0x0003, - HID_USAGE_CONSUMER_MICROPHONE = 0x0004, - HID_USAGE_CONSUMER_HEADPHONE = 0x0005, - HID_USAGE_CONSUMER_GRAPHIC_EQUALIZER = 0x0006, - // 07-1F Reserved - - HID_USAGE_CONSUMER_PLUS_10 = 0x0020, - HID_USAGE_CONSUMER_PLUS_100 = 0x0021, - HID_USAGE_CONSUMER_AM_PM = 0x0022, + HID_USAGE_CONSUMER_CONTROL = 0x0001, // CA + HID_USAGE_CONSUMER_NUMERIC_KEY_PAD = 0x0002, // NAry + HID_USAGE_CONSUMER_PROGRAMMABLE_BUTTONS = 0x0003, // NAry + HID_USAGE_CONSUMER_MICROPHONE = 0x0004, // CA + HID_USAGE_CONSUMER_HEADPHONE = 0x0005, // CA + HID_USAGE_CONSUMER_GRAPHIC_EQUALIZER = 0x0006, // CA + HID_USAGE_CONSUMER_KEYBOARD_BACKLIGHT = 0x0007, // CA + // 08-1F Reserved + + HID_USAGE_CONSUMER_PLUS_10 = 0x0020, // OSC + HID_USAGE_CONSUMER_PLUS_100 = 0x0021, // OSC + HID_USAGE_CONSUMER_AM_PM = 0x0022, // OSC // 23-3F Reserved // Power Control - HID_USAGE_CONSUMER_POWER = 0x0030, - HID_USAGE_CONSUMER_RESET = 0x0031, - HID_USAGE_CONSUMER_SLEEP = 0x0032, - - HID_USAGE_CONSUMER_SLEEP_AFTER = 0x0033, - HID_USAGE_CONSUMER_SLEEP_MODE = 0x0034, - HID_USAGE_CONSUMER_ILLUMINATION = 0x0035, - HID_USAGE_CONSUMER_FUNCTION_BUTTONS = 0x0036, + HID_USAGE_CONSUMER_POWER = 0x0030, // OOC + HID_USAGE_CONSUMER_RESET = 0x0031, // OSC + HID_USAGE_CONSUMER_SLEEP = 0x0032, // OSC + + HID_USAGE_CONSUMER_SLEEP_AFTER = 0x0033, // OSC + HID_USAGE_CONSUMER_SLEEP_MODE = 0x0034, // RTC + HID_USAGE_CONSUMER_ILLUMINATION = 0x0035, // OOC + HID_USAGE_CONSUMER_FUNCTION_BUTTONS = 0x0036, // NAry // 37-3F Reserved - HID_USAGE_CONSUMER_MENU = 0x0040, - HID_USAGE_CONSUMER_MENU_PICK = 0x0041, - HID_USAGE_CONSUMER_MENU_UP = 0x0042, - HID_USAGE_CONSUMER_MENU_DOWN = 0x0043, - HID_USAGE_CONSUMER_MENU_LEFT = 0x0044, - HID_USAGE_CONSUMER_MENU_RIGHT = 0x0045, - HID_USAGE_CONSUMER_MENU_ESCAPE = 0x0046, - HID_USAGE_CONSUMER_MENU_VALUE_INCREASE = 0x0047, - HID_USAGE_CONSUMER_MENU_VALUE_DECREASE = 0x0048, + HID_USAGE_CONSUMER_MENU = 0x0040, // OOC + HID_USAGE_CONSUMER_MENU_PICK = 0x0041, // OSC + HID_USAGE_CONSUMER_MENU_UP = 0x0042, // OSC + HID_USAGE_CONSUMER_MENU_DOWN = 0x0043, // OSC + HID_USAGE_CONSUMER_MENU_LEFT = 0x0044, // OSC + HID_USAGE_CONSUMER_MENU_RIGHT = 0x0045, // OSC + HID_USAGE_CONSUMER_MENU_ESCAPE = 0x0046, // OSC + HID_USAGE_CONSUMER_MENU_VALUE_INCREASE = 0x0047, // OSC + HID_USAGE_CONSUMER_MENU_VALUE_DECREASE = 0x0048, // OSC // 49-5F Reserved - HID_USAGE_CONSUMER_DATA_ON_SCREEN = 0x0060, - HID_USAGE_CONSUMER_CLOSED_CAPTION = 0x0061, - HID_USAGE_CONSUMER_CLOSED_CAPTION_SELECT = 0x0062, - HID_USAGE_CONSUMER_VCR_TV = 0x0063, - HID_USAGE_CONSUMER_BROADCAST_MODE = 0x0064, - HID_USAGE_CONSUMER_SNAPSHOT = 0x0065, - HID_USAGE_CONSUMER_STILL = 0x0066, - - // 67-7F Reserved - // Screen Brightness - HID_USAGE_CONSUMER_BRIGHTNESS_INCREMENT = 0x006F, - HID_USAGE_CONSUMER_BRIGHTNESS_DECREMENT = 0x0070, - - // These HID usages operate only on mobile systems (battery powered) and - // require Windows 8 (build 8302 or greater). - HID_USAGE_CONSUMER_WIRELESS_RADIO_CONTROLS = 0x000C, - HID_USAGE_CONSUMER_WIRELESS_RADIO_BUTTONS = 0x00C6, - HID_USAGE_CONSUMER_WIRELESS_RADIO_LED = 0x00C7, - HID_USAGE_CONSUMER_WIRELESS_RADIO_SLIDER_SWITCH = 0x00C8, - - HID_USAGE_CONSUMER_SELECTION = 0x0080, - HID_USAGE_CONSUMER_ASSIGN_SELECTION = 0x0081, - HID_USAGE_CONSUMER_MODE_STEP = 0x0082, - HID_USAGE_CONSUMER_RECALL_LAST = 0x0083, - HID_USAGE_CONSUMER_ENTER_CHANNEL = 0x0084, - HID_USAGE_CONSUMER_ORDER_MOVIE = 0x0085, - HID_USAGE_CONSUMER_CHANNEL = 0x0086, - HID_USAGE_CONSUMER_MEDIA_SELECTION = 0x0087, - HID_USAGE_CONSUMER_MEDIA_SELECT_COMPUTER = 0x0088, - HID_USAGE_CONSUMER_MEDIA_SELECT_TV = 0x0089, - HID_USAGE_CONSUMER_MEDIA_SELECT_WWW = 0x008A, - HID_USAGE_CONSUMER_MEDIA_SELECT_DVD = 0x008B, - HID_USAGE_CONSUMER_MEDIA_SELECT_TELEPHONE = 0x008C, - HID_USAGE_CONSUMER_MEDIA_SELECT_PROGRAM_GUIDE = 0x008D, - HID_USAGE_CONSUMER_MEDIA_SELECT_VIDEO_PHONE = 0x008E, - HID_USAGE_CONSUMER_MEDIA_SELECT_GAMES = 0x008F, - HID_USAGE_CONSUMER_MEDIA_SELECT_MESSAGES = 0x0090, - HID_USAGE_CONSUMER_MEDIA_SELECT_CD = 0x0091, - HID_USAGE_CONSUMER_MEDIA_SELECT_VCR = 0x0092, - HID_USAGE_CONSUMER_MEDIA_SELECT_TUNER = 0x0093, - HID_USAGE_CONSUMER_QUIT = 0x0094, - HID_USAGE_CONSUMER_HELP = 0x0095, - HID_USAGE_CONSUMER_MEDIA_SELECT_TAPE = 0x0096, - HID_USAGE_CONSUMER_MEDIA_SELECT_CABLE = 0x0097, - HID_USAGE_CONSUMER_MEDIA_SELECT_SATELLITE = 0x0098, - HID_USAGE_CONSUMER_MEDIA_SELECT_SECURITY = 0x0099, - HID_USAGE_CONSUMER_MEDIA_SELECT_HOME = 0x009A, - HID_USAGE_CONSUMER_MEDIA_SELECT_CALL = 0x009B, - HID_USAGE_CONSUMER_CHANNEL_INCREMENT = 0x009C, - HID_USAGE_CONSUMER_CHANNEL_DECREMENT = 0x009D, - HID_USAGE_CONSUMER_MEDIA_SELECT_SAP = 0x009E, + HID_USAGE_CONSUMER_DATA_ON_SCREEN = 0x0060, // OOC + HID_USAGE_CONSUMER_CLOSED_CAPTION = 0x0061, // OOC + HID_USAGE_CONSUMER_CLOSED_CAPTION_SELECT = 0x0062, // OSC + HID_USAGE_CONSUMER_VCR_TV = 0x0063, // OOC + HID_USAGE_CONSUMER_BROADCAST_MODE = 0x0064, // OSC + HID_USAGE_CONSUMER_SNAPSHOT = 0x0065, // OSC + HID_USAGE_CONSUMER_STILL = 0x0066, // OSC + HID_USAGE_CONSUMER_PICTURE_IN_PICTURE_TOGGLE = 0x0067, // OSC + HID_USAGE_CONSUMER_PICTURE_IN_PICTURE_SWAP = 0x0068, // OSC + HID_USAGE_CONSUMER_RED_MENU_BUTTON = 0x0069, // MC + HID_USAGE_CONSUMER_GREEN_MENU_BUTTON = 0x006A, // MC + HID_USAGE_CONSUMER_BLUE_MENU_BUTTON = 0x006B, // MC + HID_USAGE_CONSUMER_YELLOW_MENU_BUTTON = 0x006C, // MC + HID_USAGE_CONSUMER_ASPECT = 0x006D, // OSC + HID_USAGE_CONSUMER_3D_MODE_SELECT = 0x006E, // OSC + HID_USAGE_CONSUMER_DISPLAY_BRIGHTNESS_INCREMENT = 0x006F, // RTC + HID_USAGE_CONSUMER_DISPLAY_BRIGHTNESS_DECREMENT = 0x0070, // RTC + HID_USAGE_CONSUMER_DISPLAY_BRIGHTNESS = 0x0071, // LC + HID_USAGE_CONSUMER_DISPLAY_BACKLIGHT_TOGGLE = 0x0072, // OOC + HID_USAGE_CONSUMER_DISPLAY_SET_BRIGHTNESS_TO_MINIMUM = 0x0073, // OSC + HID_USAGE_CONSUMER_DISPLAY_SET_BRIGHTNESS_TO_MAXIMUM = 0x0074, // OSC + HID_USAGE_CONSUMER_DISPLAY_SET_AUTO_BRIGHTNESS = 0x0075, // OOC + HID_USAGE_CONSUMER_CAMERA_ACCESS_ENABLED = 0x0076, // OOC + HID_USAGE_CONSUMER_CAMERA_ACCESS_DISABLED = 0x0077, // OOC + HID_USAGE_CONSUMER_CAMERA_ACCESS_TOGGLE = 0x0078, // OOC + HID_USAGE_CONSUMER_KEYBOARD_BRIGHTNESS_INCREMENT = 0x0079, // OSC + HID_USAGE_CONSUMER_KEYBOARD_BRIGHTNESS_DECREMENT = 0x007A, // OSC + HID_USAGE_CONSUMER_KEYBOARD_BACKLIGHT_SET_LEVEL = 0x007B, // LC + HID_USAGE_CONSUMER_KEYBOARD_BACKLIGHT_OOC = 0x007C, // OOC + HID_USAGE_CONSUMER_KEYBOARD_BACKLIGHT_SET_MINIMUM = 0x007D, // OSC + HID_USAGE_CONSUMER_KEYBOARD_BACKLIGHT_SET_MAXIMUM = 0x007E, // OSC + HID_USAGE_CONSUMER_KEYBOARD_BACKLIGHT_AUTO = 0x007F, // OOC + HID_USAGE_CONSUMER_SELECTION = 0x0080, // NAry + HID_USAGE_CONSUMER_ASSIGN_SELECTION = 0x0081, // OSC + HID_USAGE_CONSUMER_MODE_STEP = 0x0082, // OSC + HID_USAGE_CONSUMER_RECALL_LAST = 0x0083, // OSC + HID_USAGE_CONSUMER_ENTER_CHANNEL = 0x0084, // OSC + HID_USAGE_CONSUMER_ORDER_MOVIE = 0x0085, // OSC + HID_USAGE_CONSUMER_CHANNEL = 0x0086, // LC + HID_USAGE_CONSUMER_MEDIA_SELECTION = 0x0087, // NAry + HID_USAGE_CONSUMER_MEDIA_SELECT_COMPUTER = 0x0088, // Sel + HID_USAGE_CONSUMER_MEDIA_SELECT_TV = 0x0089, // Sel + HID_USAGE_CONSUMER_MEDIA_SELECT_WWW = 0x008A, // Sel + HID_USAGE_CONSUMER_MEDIA_SELECT_DVD = 0x008B, // Sel + HID_USAGE_CONSUMER_MEDIA_SELECT_TELEPHONE = 0x008C, // Sel + HID_USAGE_CONSUMER_MEDIA_SELECT_PROGRAM_GUIDE = 0x008D, // Sel + HID_USAGE_CONSUMER_MEDIA_SELECT_VIDEO_PHONE = 0x008E, // Sel + HID_USAGE_CONSUMER_MEDIA_SELECT_GAMES = 0x008F, // Sel + HID_USAGE_CONSUMER_MEDIA_SELECT_MESSAGES = 0x0090, // Sel + HID_USAGE_CONSUMER_MEDIA_SELECT_CD = 0x0091, // Sel + HID_USAGE_CONSUMER_MEDIA_SELECT_VCR = 0x0092, // Sel + HID_USAGE_CONSUMER_MEDIA_SELECT_TUNER = 0x0093, // Sel + HID_USAGE_CONSUMER_QUIT = 0x0094, // OSC + HID_USAGE_CONSUMER_HELP = 0x0095, // OOC + HID_USAGE_CONSUMER_MEDIA_SELECT_TAPE = 0x0096, // Sel + HID_USAGE_CONSUMER_MEDIA_SELECT_CABLE = 0x0097, // Sel + HID_USAGE_CONSUMER_MEDIA_SELECT_SATELLITE = 0x0098, // Sel + HID_USAGE_CONSUMER_MEDIA_SELECT_SECURITY = 0x0099, // Sel + HID_USAGE_CONSUMER_MEDIA_SELECT_HOME = 0x009A, // Sel + HID_USAGE_CONSUMER_MEDIA_SELECT_CALL = 0x009B, // Sel + HID_USAGE_CONSUMER_CHANNEL_INCREMENT = 0x009C, // OSC + HID_USAGE_CONSUMER_CHANNEL_DECREMENT = 0x009D, // OSC + HID_USAGE_CONSUMER_MEDIA_SELECT_SAP = 0x009E, // Sel // 9F Reserved - HID_USAGE_CONSUMER_VCR_PLUS = 0x00A0, - HID_USAGE_CONSUMER_ONCE = 0x00A1, - HID_USAGE_CONSUMER_DAILY = 0x00A2, - HID_USAGE_CONSUMER_WEEKLY = 0x00A3, - HID_USAGE_CONSUMER_MONTHLY = 0x00A4, + HID_USAGE_CONSUMER_VCR_PLUS = 0x00A0, // OSC + HID_USAGE_CONSUMER_ONCE = 0x00A1, // OSC + HID_USAGE_CONSUMER_DAILY = 0x00A2, // OSC + HID_USAGE_CONSUMER_WEEKLY = 0x00A3, // OSC + HID_USAGE_CONSUMER_MONTHLY = 0x00A4, // OSC // A5-AF Reserved - HID_USAGE_CONSUMER_PLAY = 0x00B0, - HID_USAGE_CONSUMER_PAUSE = 0x00B1, - HID_USAGE_CONSUMER_RECORD = 0x00B2, - HID_USAGE_CONSUMER_FAST_FORWARD = 0x00B3, - HID_USAGE_CONSUMER_REWIND = 0x00B4, - HID_USAGE_CONSUMER_SCAN_NEXT_TRACK = 0x00B5, - HID_USAGE_CONSUMER_SCAN_PREVIOUS_TRACK = 0x00B6, - HID_USAGE_CONSUMER_STOP = 0x00B7, - HID_USAGE_CONSUMER_EJECT = 0x00B8, - HID_USAGE_CONSUMER_RANDOM_PLAY = 0x00B9, - HID_USAGE_CONSUMER_SELECT_DISC = 0x00BA, - HID_USAGE_CONSUMER_ENTER_DISC = 0x00BB, - HID_USAGE_CONSUMER_REPEAT = 0x00BC, - HID_USAGE_CONSUMER_TRACKING = 0x00BD, - HID_USAGE_CONSUMER_TRACK_NORMAL = 0x00BE, - HID_USAGE_CONSUMER_SLOW_TRACKING = 0x00BF, - HID_USAGE_CONSUMER_FRAME_FORWARD = 0x00C0, - HID_USAGE_CONSUMER_FRAME_BACK = 0x00C1, - HID_USAGE_CONSUMER_MARK = 0x00C2, - HID_USAGE_CONSUMER_CLEAR_MARK = 0x00C3, - HID_USAGE_CONSUMER_REPEAT_FROM_MARK = 0x00C4, - HID_USAGE_CONSUMER_RETURN_TO_MARK = 0x00C5, - HID_USAGE_CONSUMER_SEARCH_MARK_FORWARD = 0x00C6, - HID_USAGE_CONSUMER_SEARCH_MARK_BACKWARDS = 0x00C7, - HID_USAGE_CONSUMER_COUNTER_RESET = 0x00C8, - HID_USAGE_CONSUMER_SHOW_COUNTER = 0x00C9, - HID_USAGE_CONSUMER_TRACKING_INCREMENT = 0x00CA, - HID_USAGE_CONSUMER_TRACKING_DECREMENT = 0x00CB, - HID_USAGE_CONSUMER_STOP_EJECT = 0x00CC, - - - // Media Control - HID_USAGE_CONSUMER_PLAY_PAUSE = 0x00CD, - - HID_USAGE_CONSUMER_PLAY_SKIP = 0x00CE, - - // CF-DF Reserved - HID_USAGE_CONSUMER_VOLUME = 0x00E0, - HID_USAGE_CONSUMER_BALANCE = 0x00E1, - HID_USAGE_CONSUMER_MUTE = 0x00E2, - HID_USAGE_CONSUMER_BASS = 0x00E3, - HID_USAGE_CONSUMER_TREBLE = 0x00E4, - HID_USAGE_CONSUMER_BASS_BOOST = 0x00E5, - HID_USAGE_CONSUMER_SURROUND_MODE = 0x00E6, - HID_USAGE_CONSUMER_LOUDNESS = 0x00E7, - HID_USAGE_CONSUMER_MPX = 0x00E8, - HID_USAGE_CONSUMER_VOLUME_INCREMENT = 0x00E9, - HID_USAGE_CONSUMER_VOLUME_DECREMENT = 0x00EA, + HID_USAGE_CONSUMER_PLAY = 0x00B0, // OOC + HID_USAGE_CONSUMER_PAUSE = 0x00B1, // OOC + HID_USAGE_CONSUMER_RECORD = 0x00B2, // OOC + HID_USAGE_CONSUMER_FAST_FORWARD = 0x00B3, // OOC + HID_USAGE_CONSUMER_REWIND = 0x00B4, // OOC + HID_USAGE_CONSUMER_SCAN_NEXT_TRACK = 0x00B5, // OSC + HID_USAGE_CONSUMER_SCAN_PREVIOUS_TRACK = 0x00B6, // OSC + HID_USAGE_CONSUMER_STOP = 0x00B7, // OSC + HID_USAGE_CONSUMER_EJECT = 0x00B8, // OSC + HID_USAGE_CONSUMER_RANDOM_PLAY = 0x00B9, // OOC + HID_USAGE_CONSUMER_SELECT_DISC = 0x00BA, // NAry + HID_USAGE_CONSUMER_ENTER_DISC = 0x00BB, // MC + HID_USAGE_CONSUMER_REPEAT = 0x00BC, // OSC + HID_USAGE_CONSUMER_TRACKING = 0x00BD, // LC + HID_USAGE_CONSUMER_TRACK_NORMAL = 0x00BE, // OSC + HID_USAGE_CONSUMER_SLOW_TRACKING = 0x00BF, // LC + HID_USAGE_CONSUMER_FRAME_FORWARD = 0x00C0, // RTC + HID_USAGE_CONSUMER_FRAME_BACK = 0x00C1, // RTC + HID_USAGE_CONSUMER_MARK = 0x00C2, // OSC + HID_USAGE_CONSUMER_CLEAR_MARK = 0x00C3, // OSC + HID_USAGE_CONSUMER_REPEAT_FROM_MARK = 0x00C4, // OOC + HID_USAGE_CONSUMER_RETURN_TO_MARK = 0x00C5, // OSC + HID_USAGE_CONSUMER_SEARCH_MARK_FORWARD = 0x00C6, // OSC + HID_USAGE_CONSUMER_SEARCH_MARK_BACKWARDS = 0x00C7, // OSC + HID_USAGE_CONSUMER_COUNTER_RESET = 0x00C8, // OSC + + + // These HID usages operate only on mobile systems (battery powered) and + // require Windows 8 (build 8302 or greater). + HID_USAGE_CONSUMER_WIRELESS_RADIO_CONTROLS = 0x000C, + HID_USAGE_CONSUMER_WIRELESS_RADIO_BUTTONS = 0x00C6, + HID_USAGE_CONSUMER_WIRELESS_RADIO_LED = 0x00C7, + HID_USAGE_CONSUMER_WIRELESS_RADIO_SLIDER_SWITCH = 0x00C8, + + + HID_USAGE_CONSUMER_SHOW_COUNTER = 0x00C9, // OSC + HID_USAGE_CONSUMER_TRACKING_INCREMENT = 0x00CA, // RTC + HID_USAGE_CONSUMER_TRACKING_DECREMENT = 0x00CB, // RTC + HID_USAGE_CONSUMER_STOP_EJECT = 0x00CC, // OSC + HID_USAGE_CONSUMER_PLAY_PAUSE = 0x00CD, // OSC + HID_USAGE_CONSUMER_PLAY_SKIP = 0x00CE, // OSC + HID_USAGE_CONSUMER_VOICE_COMMAND = 0x00CF, // OSC + HID_USAGE_CONSUMER_INVOKE_CAPTURE_INTERFACE = 0x00D0, // Sel + HID_USAGE_CONSUMER_START_OR_STOP_GAME_RECORDING = 0x00D1, // Sel + HID_USAGE_CONSUMER_HISTORICAL_GAME_CAPTURE = 0x00D2, // Sel + HID_USAGE_CONSUMER_CAPTURE_GAME_SCREENSHOT = 0x00D3, // Sel + HID_USAGE_CONSUMER_SHOW_OR_HIDE_RECORDING_INDICATOR = 0x00D4, // Sel + HID_USAGE_CONSUMER_START_OR_STOP_MICROPHONE_CAPTURE = 0x00D5, // Sel + HID_USAGE_CONSUMER_START_OR_STOP_CAMERA_CAPTURE = 0x00D6, // Sel + HID_USAGE_CONSUMER_START_OR_STOP_GAME_BROADCAST = 0x00D7, // Sel + HID_USAGE_CONSUMER_START_OR_STOP_VOICE_DICTATION_SESSION = 0x00D8, // OOC + HID_USAGE_CONSUMER_INVOKE_DISMISS_EMOJI_PICKER = 0x00D9, // OOC + // DA-DF Reserved + HID_USAGE_CONSUMER_VOLUME = 0x00E0, // LC + HID_USAGE_CONSUMER_BALANCE = 0x00E1, // LC + HID_USAGE_CONSUMER_MUTE = 0x00E2, // OOC + HID_USAGE_CONSUMER_BASS = 0x00E3, // LC + HID_USAGE_CONSUMER_TREBLE = 0x00E4, // LC + HID_USAGE_CONSUMER_BASS_BOOST = 0x00E5, // OOC + HID_USAGE_CONSUMER_SURROUND_MODE = 0x00E6, // OSC + HID_USAGE_CONSUMER_LOUDNESS = 0x00E7, // OOC + HID_USAGE_CONSUMER_MPX = 0x00E8, // OOC + HID_USAGE_CONSUMER_VOLUME_INCREMENT = 0x00E9, // RTC + HID_USAGE_CONSUMER_VOLUME_DECREMENT = 0x00EA, // RTC // EB-EF Reserved - HID_USAGE_CONSUMER_SPEED_SELECT = 0x00F0, - HID_USAGE_CONSUMER_PLAYBACK_SPEED = 0x00F1, - HID_USAGE_CONSUMER_STANDARD_PLAY = 0x00F2, - HID_USAGE_CONSUMER_LONG_PLAY = 0x00F3, - HID_USAGE_CONSUMER_EXTENDED_PLAY = 0x00F4, - HID_USAGE_CONSUMER_SLOW = 0x00F5, + HID_USAGE_CONSUMER_SPEED_SELECT = 0x00F0, // OSC + HID_USAGE_CONSUMER_PLAYBACK_SPEED = 0x00F1, // NAry + HID_USAGE_CONSUMER_STANDARD_PLAY = 0x00F2, // Sel + HID_USAGE_CONSUMER_LONG_PLAY = 0x00F3, // Sel + HID_USAGE_CONSUMER_EXTENDED_PLAY = 0x00F4, // Sel + HID_USAGE_CONSUMER_SLOW = 0x00F5, // OSC // F6-FF Reserved - HID_USAGE_CONSUMER_FAN_ENABLE = 0x0100, - HID_USAGE_CONSUMER_FAN_SPEED = 0x0101, - HID_USAGE_CONSUMER_LIGHT_ENABLE = 0x0102, - HID_USAGE_CONSUMER_LIGHT_ILLUMINATION_LEVEL = 0x0103, - HID_USAGE_CONSUMER_CLIMATE_CONTROL_ENABLE = 0x0104, - HID_USAGE_CONSUMER_ROOM_TEMPERATURE = 0x0105, - HID_USAGE_CONSUMER_SECURITY_ENABLE = 0x0106, - HID_USAGE_CONSUMER_FIRE_ALARM = 0x0107, - HID_USAGE_CONSUMER_POLICE_ALARM = 0x0108, - HID_USAGE_CONSUMER_PROXIMITY = 0x0109, - HID_USAGE_CONSUMER_MOTION = 0x010A, - HID_USAGE_CONSUMER_DURESS_ALARM = 0x010B, - HID_USAGE_CONSUMER_HOLDUP_ALARM = 0x010C, - HID_USAGE_CONSUMER_MEDICAL_ALARM = 0x010D, + HID_USAGE_CONSUMER_FAN_ENABLE = 0x0100, // OOC + HID_USAGE_CONSUMER_FAN_SPEED = 0x0101, // LC + HID_USAGE_CONSUMER_LIGHT_ENABLE = 0x0102, // OOC + HID_USAGE_CONSUMER_LIGHT_ILLUMINATION_LEVEL = 0x0103, // LC + HID_USAGE_CONSUMER_CLIMATE_CONTROL_ENABLE = 0x0104, // OOC + HID_USAGE_CONSUMER_ROOM_TEMPERATURE = 0x0105, // LC + HID_USAGE_CONSUMER_SECURITY_ENABLE = 0x0106, // OOC + HID_USAGE_CONSUMER_FIRE_ALARM = 0x0107, // OSC + HID_USAGE_CONSUMER_POLICE_ALARM = 0x0108, // OSC + HID_USAGE_CONSUMER_PROXIMITY = 0x0109, // LC + HID_USAGE_CONSUMER_MOTION = 0x010A, // OSC + HID_USAGE_CONSUMER_DURESS_ALARM = 0x010B, // OSC + HID_USAGE_CONSUMER_HOLDUP_ALARM = 0x010C, // OSC + HID_USAGE_CONSUMER_MEDICAL_ALARM = 0x010D, // OSC // 10E-14F Reserved - HID_USAGE_CONSUMER_BALANCE_RIGHT = 0x0150, - HID_USAGE_CONSUMER_BALANCE_LEFT = 0x0151, - HID_USAGE_CONSUMER_BASS_INCREMENT = 0x0152, - HID_USAGE_CONSUMER_BASS_DECREMENT = 0x0153, - HID_USAGE_CONSUMER_TREBLE_INCREMENT = 0x0154, - HID_USAGE_CONSUMER_TREBLE_DECREMENT = 0x0155, + HID_USAGE_CONSUMER_BALANCE_RIGHT = 0x0150, // RTC + HID_USAGE_CONSUMER_BALANCE_LEFT = 0x0151, // RTC + HID_USAGE_CONSUMER_BASS_INCREMENT = 0x0152, // RTC + HID_USAGE_CONSUMER_BASS_DECREMENT = 0x0153, // RTC + HID_USAGE_CONSUMER_TREBLE_INCREMENT = 0x0154, // RTC + HID_USAGE_CONSUMER_TREBLE_DECREMENT = 0x0155, // RTC // 156-15F Reserved - HID_USAGE_CONSUMER_SPEAKER_SYSTEM = 0x0160, - HID_USAGE_CONSUMER_CHANNEL_LEFT = 0x0161, - HID_USAGE_CONSUMER_CHANNEL_RIGHT = 0x0162, - HID_USAGE_CONSUMER_CHANNEL_CENTER = 0x0163, - HID_USAGE_CONSUMER_CHANNEL_FRONT = 0x0164, - HID_USAGE_CONSUMER_CHANNEL_CENTER_FRONT = 0x0165, - HID_USAGE_CONSUMER_CHANNEL_SIDE = 0x0166, - HID_USAGE_CONSUMER_CHANNEL_SURROUND = 0x0167, - HID_USAGE_CONSUMER_CHANNEL_LOW_FREQUENCY = 0x0168, - // Enhancement - // CL 15.12.1 - HID_USAGE_CONSUMER_CHANNEL_TOP = 0x0169, - HID_USAGE_CONSUMER_CHANNEL_UNKNOWN = 0x016A, + HID_USAGE_CONSUMER_SPEAKER_SYSTEM = 0x0160, // CL + HID_USAGE_CONSUMER_CHANNEL_LEFT = 0x0161, // CL + HID_USAGE_CONSUMER_CHANNEL_RIGHT = 0x0162, // CL + HID_USAGE_CONSUMER_CHANNEL_CENTER = 0x0163, // CL + HID_USAGE_CONSUMER_CHANNEL_FRONT = 0x0164, // CL + HID_USAGE_CONSUMER_CHANNEL_CENTER_FRONT = 0x0165, // CL + HID_USAGE_CONSUMER_CHANNEL_SIDE = 0x0166, // CL + HID_USAGE_CONSUMER_CHANNEL_SURROUND = 0x0167, // CL + HID_USAGE_CONSUMER_CHANNEL_LOW_FREQUENCY = 0x0168, // CL + HID_USAGE_CONSUMER_CHANNEL_TOP = 0x0169, // CL + HID_USAGE_CONSUMER_CHANNEL_UNKNOWN = 0x016A, // CL // 16B-16F Reserved - HID_USAGE_CONSUMER_SUB_CHANNEL = 0x0170, - HID_USAGE_CONSUMER_SUB_CHANNEL_INCREMENT = 0x0171, - HID_USAGE_CONSUMER_SUB_CHANNEL_DECREMENT = 0x0172, - HID_USAGE_CONSUMER_ALTERNATE_AUDIO_INCREMENT = 0x0173, - HID_USAGE_CONSUMER_ALTERNATE_AUDIO_DECREMENT = 0x0174, + HID_USAGE_CONSUMER_SUB_CHANNEL = 0x0170, // LC + HID_USAGE_CONSUMER_SUB_CHANNEL_INCREMENT = 0x0171, // OSC + HID_USAGE_CONSUMER_SUB_CHANNEL_DECREMENT = 0x0172, // OSC + HID_USAGE_CONSUMER_ALTERNATE_AUDIO_INCREMENT = 0x0173, // OSC + HID_USAGE_CONSUMER_ALTERNATE_AUDIO_DECREMENT = 0x0174, // OSC // 175-17F Reserved - HID_USAGE_CONSUMER_APPLICATION_LAUNCH_BUTTONS = 0x0180, - HID_USAGE_CONSUMER_AL_LAUNCH_BUTTON_CONFIGURATION = 0x0181, - // Tool - // Sel 15.15 - HID_USAGE_CONSUMER_AL_PROGRAMMABLE_BUTTON = 0x0182, - // Configuration - // Sel 15.15 - HID_USAGE_CONSUMER_AL_CONSUMER_CONTROL_CONFIGURATION = 0x0183, - // Configuration - // Sel 15.15 - HID_USAGE_CONSUMER_AL_WORD_PROCESSOR = 0x0184, - HID_USAGE_CONSUMER_AL_TEXT_EDITOR = 0x0185, - HID_USAGE_CONSUMER_AL_SPREADSHEET = 0x0186, - HID_USAGE_CONSUMER_AL_GRAPHICS_EDITOR = 0x0187, - HID_USAGE_CONSUMER_AL_PRESENTATION_APP = 0x0188, - HID_USAGE_CONSUMER_AL_DATABASE_APP = 0x0189, - HID_USAGE_CONSUMER_AL_EMAIL_READER = 0x018A, - HID_USAGE_CONSUMER_AL_NEWSREADER = 0x018B, - HID_USAGE_CONSUMER_AL_VOICEMAIL = 0x018C, - HID_USAGE_CONSUMER_AL_CONTACTS_ADDRESS_BOOK = 0x018D, - HID_USAGE_CONSUMER_AL_CALENDAR_SCHEDULE = 0x018E, - HID_USAGE_CONSUMER_AL_TASK_PROJECT_MANAGER = 0x018F, - HID_USAGE_CONSUMER_AL_LOG_JOURNAL_TIMECARD = 0x0190, - HID_USAGE_CONSUMER_AL_CHECKBOOK_FINANCE = 0x0191, - HID_USAGE_CONSUMER_AL_CALCULATOR = 0x0192, - HID_USAGE_CONSUMER_AL_A_V_CAPTURE_PLAYBACK = 0x0193, - HID_USAGE_CONSUMER_AL_LOCAL_MACHINE_BROWSER = 0x0194, - HID_USAGE_CONSUMER_AL_LAN_WAN_BROWSER = 0x0195, - HID_USAGE_CONSUMER_AL_INTERNET_BROWSER = 0x0196, - HID_USAGE_CONSUMER_AL_REMOTE_NETWORKING_ISP = 0x0197, - // Connect - // Sel 15.15 - HID_USAGE_CONSUMER_AL_NETWORK_CONFERENCE = 0x0198, - HID_USAGE_CONSUMER_AL_NETWORK_CHAT = 0x0199, - HID_USAGE_CONSUMER_AL_TELEPHONY_DIALER = 0x019A, - HID_USAGE_CONSUMER_AL_LOGON = 0x019B, - HID_USAGE_CONSUMER_AL_LOGOFF = 0x019C, - HID_USAGE_CONSUMER_AL_LOGON_LOGOFF = 0x019D, - HID_USAGE_CONSUMER_AL_TERMINAL_LOCK_SCREENSAVER = 0x019E, - HID_USAGE_CONSUMER_AL_CONTROL_PANEL = 0x019F, - HID_USAGE_CONSUMER_AL_COMMAND_LINE_PROCESSOR_RUN = 0x01A0, - HID_USAGE_CONSUMER_AL_PROCESS_TASK_MANAGER = 0x01A1, - HID_USAGE_CONSUMER_AL_SELECT_TASK_APPLICATION = 0x01A2, - HID_USAGE_CONSUMER_AL_NEXT_TASK_APPLICATION = 0x01A3, - HID_USAGE_CONSUMER_AL_PREVIOUS_TASK_APPLICATION = 0x01A4, - HID_USAGE_CONSUMER_AL_PREEMPTIVE_HALT = 0x01A5, - // Task_Application - // Sel 15.15 - HID_USAGE_CONSUMER_AL_INTEGRATED_HELP_CENTER = 0x01A6, - HID_USAGE_CONSUMER_AL_DOCUMENTS = 0x01A7, - HID_USAGE_CONSUMER_AL_THESAURUS = 0x01A8, - HID_USAGE_CONSUMER_AL_DICTIONARY = 0x01A9, - HID_USAGE_CONSUMER_AL_DESKTOP = 0x01AA, - HID_USAGE_CONSUMER_AL_SPELL_CHECK = 0x01AB, - HID_USAGE_CONSUMER_AL_GRAMMAR_CHECK = 0x01AC, - HID_USAGE_CONSUMER_AL_WIRELESS_STATUS = 0x01AD, - HID_USAGE_CONSUMER_AL_KEYBOARD_LAYOUT = 0x01AE, - HID_USAGE_CONSUMER_AL_VIRUS_PROTECTION = 0x01AF, - HID_USAGE_CONSUMER_AL_ENCRYPTION = 0x01B0, - HID_USAGE_CONSUMER_AL_SCREEN_SAVER = 0x01B1, - HID_USAGE_CONSUMER_AL_ALARMS = 0x01B2, - HID_USAGE_CONSUMER_AL_CLOCK = 0x01B3, - HID_USAGE_CONSUMER_AL_FILE_BROWSER = 0x01B4, - HID_USAGE_CONSUMER_AL_POWER_STATUS = 0x01B5, - HID_USAGE_CONSUMER_AL_IMAGE_BROWSER = 0x01B6, - HID_USAGE_CONSUMER_AL_AUDIO_BROWSER = 0x01B7, - HID_USAGE_CONSUMER_AL_MOVIE_BROWSER = 0x01B8, - HID_USAGE_CONSUMER_AL_DIGITAL_RIGHTS_MANAGER = 0x01B9, - HID_USAGE_CONSUMER_AL_DIGITAL_WALLET = 0x01BA, + HID_USAGE_CONSUMER_APPLICATION_LAUNCH_BUTTONS = 0x0180, // NAry + HID_USAGE_CONSUMER_AL_LAUNCH_BUTTON_CONFIGURATION = 0x0181, // Sel + HID_USAGE_CONSUMER_AL_PROGRAMMABLE_BUTTON = 0x0182, // Sel + HID_USAGE_CONSUMER_AL_CONSUMER_CONTROL_CONFIGURATION = 0x0183, // Sel + HID_USAGE_CONSUMER_AL_WORD_PROCESSOR = 0x0184, // Sel + HID_USAGE_CONSUMER_AL_TEXT_EDITOR = 0x0185, // Sel + HID_USAGE_CONSUMER_AL_SPREADSHEET = 0x0186, // Sel + HID_USAGE_CONSUMER_AL_GRAPHICS_EDITOR = 0x0187, // Sel + HID_USAGE_CONSUMER_AL_PRESENTATION_APP = 0x0188, // Sel + HID_USAGE_CONSUMER_AL_DATABASE_APP = 0x0189, // Sel + HID_USAGE_CONSUMER_AL_EMAIL_READER = 0x018A, // Sel + HID_USAGE_CONSUMER_AL_NEWSREADER = 0x018B, // Sel + HID_USAGE_CONSUMER_AL_VOICEMAIL = 0x018C, // Sel + HID_USAGE_CONSUMER_AL_CONTACTS_ADDRESS_BOOK = 0x018D, // Sel + HID_USAGE_CONSUMER_AL_CALENDAR_SCHEDULE = 0x018E, // Sel + HID_USAGE_CONSUMER_AL_TASK_PROJECT_MANAGER = 0x018F, // Sel + HID_USAGE_CONSUMER_AL_LOG_JOURNAL_TIMECARD = 0x0190, // Sel + HID_USAGE_CONSUMER_AL_CHECKBOOK_FINANCE = 0x0191, // Sel + HID_USAGE_CONSUMER_AL_CALCULATOR = 0x0192, // Sel + HID_USAGE_CONSUMER_AL_A_V_CAPTURE_PLAYBACK = 0x0193, // Sel + HID_USAGE_CONSUMER_AL_LOCAL_MACHINE_BROWSER = 0x0194, // Sel + HID_USAGE_CONSUMER_AL_LAN_WAN_BROWSER = 0x0195, // Sel + HID_USAGE_CONSUMER_AL_INTERNET_BROWSER = 0x0196, // Sel + HID_USAGE_CONSUMER_AL_REMOTE_NETWORKING_ISP = 0x0197, // Sel + HID_USAGE_CONSUMER_AL_NETWORK_CONFERENCE = 0x0198, // Sel + HID_USAGE_CONSUMER_AL_NETWORK_CHAT = 0x0199, // Sel + HID_USAGE_CONSUMER_AL_TELEPHONY_DIALER = 0x019A, // Sel + HID_USAGE_CONSUMER_AL_LOGON = 0x019B, // Sel + HID_USAGE_CONSUMER_AL_LOGOFF = 0x019C, // Sel + HID_USAGE_CONSUMER_AL_LOGON_LOGOFF = 0x019D, // Sel + HID_USAGE_CONSUMER_AL_TERMINAL_LOCK_SCREENSAVER = 0x019E, // Sel + HID_USAGE_CONSUMER_AL_CONTROL_PANEL = 0x019F, // Sel + HID_USAGE_CONSUMER_AL_COMMAND_LINE_PROCESSOR_RUN = 0x01A0, // Sel + HID_USAGE_CONSUMER_AL_PROCESS_TASK_MANAGER = 0x01A1, // Sel + HID_USAGE_CONSUMER_AL_SELECT_TASK_APPLICATION = 0x01A2, // Sel + HID_USAGE_CONSUMER_AL_NEXT_TASK_APPLICATION = 0x01A3, // Sel + HID_USAGE_CONSUMER_AL_PREVIOUS_TASK_APPLICATION = 0x01A4, // Sel + HID_USAGE_CONSUMER_AL_PREEMPTIVE_HALT = 0x01A5, // Sel + HID_USAGE_CONSUMER_AL_INTEGRATED_HELP_CENTER = 0x01A6, // Sel + HID_USAGE_CONSUMER_AL_DOCUMENTS = 0x01A7, // Sel + HID_USAGE_CONSUMER_AL_THESAURUS = 0x01A8, // Sel + HID_USAGE_CONSUMER_AL_DICTIONARY = 0x01A9, // Sel + HID_USAGE_CONSUMER_AL_DESKTOP = 0x01AA, // Sel + HID_USAGE_CONSUMER_AL_SPELL_CHECK = 0x01AB, // Sel + HID_USAGE_CONSUMER_AL_GRAMMAR_CHECK = 0x01AC, // Sel + HID_USAGE_CONSUMER_AL_WIRELESS_STATUS = 0x01AD, // Sel + HID_USAGE_CONSUMER_AL_KEYBOARD_LAYOUT = 0x01AE, // Sel + HID_USAGE_CONSUMER_AL_VIRUS_PROTECTION = 0x01AF, // Sel + HID_USAGE_CONSUMER_AL_ENCRYPTION = 0x01B0, // Sel + HID_USAGE_CONSUMER_AL_SCREEN_SAVER = 0x01B1, // Sel + HID_USAGE_CONSUMER_AL_ALARMS = 0x01B2, // Sel + HID_USAGE_CONSUMER_AL_CLOCK = 0x01B3, // Sel + HID_USAGE_CONSUMER_AL_FILE_BROWSER = 0x01B4, // Sel + HID_USAGE_CONSUMER_AL_POWER_STATUS = 0x01B5, // Sel + HID_USAGE_CONSUMER_AL_IMAGE_BROWSER = 0x01B6, // Sel + HID_USAGE_CONSUMER_AL_AUDIO_BROWSER = 0x01B7, // Sel + HID_USAGE_CONSUMER_AL_MOVIE_BROWSER = 0x01B8, // Sel + HID_USAGE_CONSUMER_AL_DIGITAL_RIGHTS_MANAGER = 0x01B9, // Sel + HID_USAGE_CONSUMER_AL_DIGITAL_WALLET = 0x01BA, // Sel // 1BB Reserved - HID_USAGE_CONSUMER_AL_INSTANT_MESSAGING = 0x01BC, - HID_USAGE_CONSUMER_AL_OEM_FEATURES_TIPS_TUTORIAL = 0x01BD, - // Browser - // Sel 15.15 - HID_USAGE_CONSUMER_AL_OEM_HELP = 0x01BE, - HID_USAGE_CONSUMER_AL_ONLINE_COMMUNITY = 0x01BF, - HID_USAGE_CONSUMER_AL_ENTERTAINMENT_CONTENT = 0x01C0, - // Browser - // Sel 15.15 - HID_USAGE_CONSUMER_AL_ONLINE_SHOPPING_BROWSER = 0x01C1, - HID_USAGE_CONSUMER_AL_SMARTCARD_INFORMATION_HELP = 0x01C2, - HID_USAGE_CONSUMER_AL_MARKET_MONITOR_FINANCE = 0x01C3, - // Browser - // Sel 15.15 - HID_USAGE_CONSUMER_AL_CUSTOMIZED_CORPORATE_NEWS = 0x01C4, - // Browser - // Sel 15.15 - HID_USAGE_CONSUMER_AL_ONLINE_ACTIVITY_BROWSER = 0x01C5, - HID_USAGE_CONSUMER_AL_RESEARCH_SEARCH_BROWSER = 0x01C6, - HID_USAGE_CONSUMER_AL_AUDIO_PLAYER = 0x01C7, - // 1C8-1FF Reserved - HID_USAGE_CONSUMER_GENERIC_GUI_APPLICATION = 0x0200, - // ' Controls - // ' - HID_USAGE_CONSUMER_AC_NEW = 0x0201, - HID_USAGE_CONSUMER_AC_OPEN = 0x0202, - HID_USAGE_CONSUMER_AC_CLOSE = 0x0203, - HID_USAGE_CONSUMER_AC_EXIT = 0x0204, - HID_USAGE_CONSUMER_AC_MAXIMIZE = 0x0205, - HID_USAGE_CONSUMER_AC_MINIMIZE = 0x0206, - HID_USAGE_CONSUMER_AC_SAVE = 0x0207, - HID_USAGE_CONSUMER_AC_PRINT = 0x0208, - HID_USAGE_CONSUMER_AC_PROPERTIES = 0x0209, - HID_USAGE_CONSUMER_AC_UNDO = 0x021A, - HID_USAGE_CONSUMER_AC_COPY = 0x021B, - HID_USAGE_CONSUMER_AC_CUT = 0x021C, - HID_USAGE_CONSUMER_AC_PASTE = 0x021D, - HID_USAGE_CONSUMER_AC_SELECT_ALL = 0x021E, - HID_USAGE_CONSUMER_AC_FIND = 0x021F, - HID_USAGE_CONSUMER_AC_FIND_AND_REPLACE = 0x0220, - // Browser/Explorer Specific - HID_USAGE_CONSUMER_AC_SEARCH = 0x0221, - HID_USAGE_CONSUMER_AC_GO_TO = 0x0222, - HID_USAGE_CONSUMER_AC_HOME = 0x0223, - HID_USAGE_CONSUMER_AC_BACK = 0x0224, - HID_USAGE_CONSUMER_AC_FORWARD = 0x0225, - HID_USAGE_CONSUMER_AC_STOP = 0x0226, - HID_USAGE_CONSUMER_AC_REFRESH = 0x0227, - HID_USAGE_CONSUMER_AC_PREVIOUS_LINK = 0x0228, - HID_USAGE_CONSUMER_AC_NEXT_LINK = 0x0229, - HID_USAGE_CONSUMER_AC_BOOKMARKS = 0x022A, - HID_USAGE_CONSUMER_AC_HISTORY = 0x022B, - HID_USAGE_CONSUMER_AC_SUBSCRIPTIONS = 0x022C, - HID_USAGE_CONSUMER_AC_ZOOM_IN = 0x022D, - HID_USAGE_CONSUMER_AC_ZOOM_OUT = 0x022E, - HID_USAGE_CONSUMER_AC_ZOOM = 0x022F, - HID_USAGE_CONSUMER_AC_FULL_SCREEN_VIEW = 0x0230, - HID_USAGE_CONSUMER_AC_NORMAL_VIEW = 0x0231, - HID_USAGE_CONSUMER_AC_VIEW_TOGGLE = 0x0232, - HID_USAGE_CONSUMER_AC_SCROLL_UP = 0x0233, - HID_USAGE_CONSUMER_AC_SCROLL_DOWN = 0x0234, - HID_USAGE_CONSUMER_AC_SCROLL = 0x0235, - HID_USAGE_CONSUMER_AC_PAN_LEFT = 0x0236, - HID_USAGE_CONSUMER_AC_PAN_RIGHT = 0x0237, - // Mouse Horizontal scroll - HID_USAGE_CONSUMER_AC_PAN = 0x0238, - HID_USAGE_CONSUMER_AC_NEW_WINDOW = 0x0239, - HID_USAGE_CONSUMER_AC_TILE_HORIZONTALLY = 0x023A, - HID_USAGE_CONSUMER_AC_TILE_VERTICALLY = 0x023B, - HID_USAGE_CONSUMER_AC_FORMAT = 0x023C, - HID_USAGE_CONSUMER_AC_EDIT = 0x023D, - HID_USAGE_CONSUMER_AC_BOLD = 0x023E, - HID_USAGE_CONSUMER_AC_ITALICS = 0x023F, - HID_USAGE_CONSUMER_AC_UNDERLINE = 0x0240, - HID_USAGE_CONSUMER_AC_STRIKETHROUGH = 0x0241, - HID_USAGE_CONSUMER_AC_SUBSCRIPT = 0x0242, - HID_USAGE_CONSUMER_AC_SUPERSCRIPT = 0x0243, - HID_USAGE_CONSUMER_AC_ALL_CAPS = 0x0244, - HID_USAGE_CONSUMER_AC_ROTATE = 0x0245, - HID_USAGE_CONSUMER_AC_RESIZE = 0x0246, - HID_USAGE_CONSUMER_AC_FLIP_HORIZONTAL = 0x0247, - HID_USAGE_CONSUMER_AC_FLIP_VERTICAL = 0x0248, - HID_USAGE_CONSUMER_AC_MIRROR_HORIZONTAL = 0x0249, - HID_USAGE_CONSUMER_AC_MIRROR_VERTICAL = 0x024A, - HID_USAGE_CONSUMER_AC_FONT_SELECT = 0x024B, - HID_USAGE_CONSUMER_AC_FONT_COLOR = 0x024C, - HID_USAGE_CONSUMER_AC_FONT_SIZE = 0x024D, - HID_USAGE_CONSUMER_AC_JUSTIFY_LEFT = 0x024E, - HID_USAGE_CONSUMER_AC_JUSTIFY_CENTER_H = 0x024F, - HID_USAGE_CONSUMER_AC_JUSTIFY_RIGHT = 0x0250, - HID_USAGE_CONSUMER_AC_JUSTIFY_BLOCK_H = 0x0251, - HID_USAGE_CONSUMER_AC_JUSTIFY_TOP = 0x0252, - HID_USAGE_CONSUMER_AC_JUSTIFY_CENTER_V = 0x0253, - HID_USAGE_CONSUMER_AC_JUSTIFY_BOTTOM = 0x0254, - HID_USAGE_CONSUMER_AC_JUSTIFY_BLOCK_V = 0x0255, - HID_USAGE_CONSUMER_AC_INDENT_DECREASE = 0x0256, - HID_USAGE_CONSUMER_AC_INDENT_INCREASE = 0x0257, - HID_USAGE_CONSUMER_AC_NUMBERED_LIST = 0x0258, - HID_USAGE_CONSUMER_AC_RESTART_NUMBERING = 0x0259, - HID_USAGE_CONSUMER_AC_BULLETED_LIST = 0x025A, - HID_USAGE_CONSUMER_AC_PROMOTE = 0x025B, - HID_USAGE_CONSUMER_AC_DEMOTE = 0x025C, - HID_USAGE_CONSUMER_AC_YES = 0x025D, - HID_USAGE_CONSUMER_AC_NO = 0x025E, - HID_USAGE_CONSUMER_AC_CANCEL = 0x025F, - HID_USAGE_CONSUMER_AC_CATALOG = 0x0260, - HID_USAGE_CONSUMER_AC_BUY_CHECKOUT = 0x0261, - HID_USAGE_CONSUMER_AC_ADD_TO_CART = 0x0262, - HID_USAGE_CONSUMER_AC_EXPAND = 0x0263, - HID_USAGE_CONSUMER_AC_EXPAND_ALL = 0x0264, - HID_USAGE_CONSUMER_AC_COLLAPSE = 0x0265, - HID_USAGE_CONSUMER_AC_COLLAPSE_ALL = 0x0266, - HID_USAGE_CONSUMER_AC_PRINT_PREVIEW = 0x0267, - HID_USAGE_CONSUMER_AC_PASTE_SPECIAL = 0x0268, - HID_USAGE_CONSUMER_AC_INSERT_MODE = 0x0269, - HID_USAGE_CONSUMER_AC_DELETE = 0x026A, - HID_USAGE_CONSUMER_AC_LOCK = 0x026B, - HID_USAGE_CONSUMER_AC_UNLOCK = 0x026C, - HID_USAGE_CONSUMER_AC_PROTECT = 0x026D, - HID_USAGE_CONSUMER_AC_UNPROTECT = 0x026E, - HID_USAGE_CONSUMER_AC_ATTACH_COMMENT = 0x026F, - HID_USAGE_CONSUMER_AC_DELETE_COMMENT = 0x0270, - HID_USAGE_CONSUMER_AC_VIEW_COMMENT = 0x0271, - HID_USAGE_CONSUMER_AC_SELECT_WORD = 0x0272, - HID_USAGE_CONSUMER_AC_SELECT_SENTENCE = 0x0273, - HID_USAGE_CONSUMER_AC_SELECT_PARAGRAPH = 0x0274, - HID_USAGE_CONSUMER_AC_SELECT_COLUMN = 0x0275, - HID_USAGE_CONSUMER_AC_SELECT_ROW = 0x0276, - HID_USAGE_CONSUMER_AC_SELECT_TABLE = 0x0277, - HID_USAGE_CONSUMER_AC_SELECT_OBJECT = 0x0278, - HID_USAGE_CONSUMER_AC_REDO_REPEAT = 0x0279, - HID_USAGE_CONSUMER_AC_SORT = 0x027A, - HID_USAGE_CONSUMER_AC_SORT_ASCENDING = 0x027B, - HID_USAGE_CONSUMER_AC_SORT_DESCENDING = 0x027C, - HID_USAGE_CONSUMER_AC_FILTER = 0x027D, - HID_USAGE_CONSUMER_AC_SET_CLOCK = 0x027E, - HID_USAGE_CONSUMER_AC_VIEW_CLOCK = 0x027F, - HID_USAGE_CONSUMER_AC_SELECT_TIME_ZONE = 0x0280, - HID_USAGE_CONSUMER_AC_EDIT_TIME_ZONES = 0x0281, - HID_USAGE_CONSUMER_AC_SET_ALARM = 0x0282, - HID_USAGE_CONSUMER_AC_CLEAR_ALARM = 0x0283, - HID_USAGE_CONSUMER_AC_SNOOZE_ALARM = 0x0284, - HID_USAGE_CONSUMER_AC_RESET_ALARM = 0x0285, - HID_USAGE_CONSUMER_AC_SYNCHRONIZE = 0x0286, - HID_USAGE_CONSUMER_AC_SEND_RECEIVE = 0x0287, - HID_USAGE_CONSUMER_AC_SEND_TO = 0x0288, - HID_USAGE_CONSUMER_AC_REPLY = 0x0289, - HID_USAGE_CONSUMER_AC_REPLY_ALL = 0x028A, - HID_USAGE_CONSUMER_AC_FORWARD_MSG = 0x028B, - HID_USAGE_CONSUMER_AC_SEND = 0x028C, - HID_USAGE_CONSUMER_AC_ATTACH_FILE = 0x028D, - HID_USAGE_CONSUMER_AC_UPLOAD = 0x028E, - HID_USAGE_CONSUMER_AC_DOWNLOAD_SAVE_TARGET_AS = 0x028F, - HID_USAGE_CONSUMER_AC_SET_BORDERS = 0x0290, - HID_USAGE_CONSUMER_AC_INSERT_ROW = 0x0291, - HID_USAGE_CONSUMER_AC_INSERT_COLUMN = 0x0292, - HID_USAGE_CONSUMER_AC_INSERT_FILE = 0x0293, - HID_USAGE_CONSUMER_AC_INSERT_PICTURE = 0x0294, - HID_USAGE_CONSUMER_AC_INSERT_OBJECT = 0x0295, - HID_USAGE_CONSUMER_AC_INSERT_SYMBOL = 0x0296, - HID_USAGE_CONSUMER_AC_SAVE_AND_CLOSE = 0x0297, - HID_USAGE_CONSUMER_AC_RENAME = 0x0298, - HID_USAGE_CONSUMER_AC_MERGE = 0x0299, - HID_USAGE_CONSUMER_AC_SPLIT = 0x029A, - HID_USAGE_CONSUMER_AC_DISRIBUTE_HORIZONTALLY = 0x029B, - HID_USAGE_CONSUMER_AC_DISTRIBUTE_VERTICALLY = 0x029C, - // 29D-FFFF Reserved - + HID_USAGE_CONSUMER_AL_INSTANT_MESSAGING = 0x01BC, // Sel + HID_USAGE_CONSUMER_AL_OEM_FEATURES_TIPS_TUTORIAL = 0x01BD, // Sel + HID_USAGE_CONSUMER_AL_OEM_HELP = 0x01BE, // Sel + HID_USAGE_CONSUMER_AL_ONLINE_COMMUNITY = 0x01BF, // Sel + HID_USAGE_CONSUMER_AL_ENTERTAINMENT_CONTENT = 0x01C0, // Sel + HID_USAGE_CONSUMER_AL_ONLINE_SHOPPING_BROWSER = 0x01C1, // Sel + HID_USAGE_CONSUMER_AL_SMARTCARD_INFORMATION_HELP = 0x01C2, // Sel + HID_USAGE_CONSUMER_AL_MARKET_MONITOR_FINANCE = 0x01C3, // Sel + HID_USAGE_CONSUMER_AL_CUSTOMIZED_CORPORATE_NEWS = 0x01C4, // Sel + HID_USAGE_CONSUMER_AL_ONLINE_ACTIVITY_BROWSER = 0x01C5, // Sel + HID_USAGE_CONSUMER_AL_RESEARCH_SEARCH_BROWSER = 0x01C6, // Sel + HID_USAGE_CONSUMER_AL_AUDIO_PLAYER = 0x01C7, // Sel + HID_USAGE_CONSUMER_AL_MESSAGE_STATUS = 0x01C8, // Sel + HID_USAGE_CONSUMER_AL_CONTACT_SYNC = 0x01C9, // Sel + HID_USAGE_CONSUMER_AL_NAVIGATION = 0x01CA, // Sel + HID_USAGE_CONSUMER_AL_CONTEXT_AWARE_DESKTOP_ASSISTANT = 0x01CB, // Sel + // 1CC-1FF Reserved + HID_USAGE_CONSUMER_GENERIC_GUI_APPLICATION = 0x0200, // NAry + HID_USAGE_CONSUMER_AC_NEW = 0x0201, // Sel + HID_USAGE_CONSUMER_AC_OPEN = 0x0202, // Sel + HID_USAGE_CONSUMER_AC_CLOSE = 0x0203, // Sel + HID_USAGE_CONSUMER_AC_EXIT = 0x0204, // Sel + HID_USAGE_CONSUMER_AC_MAXIMIZE = 0x0205, // Sel + HID_USAGE_CONSUMER_AC_MINIMIZE = 0x0206, // Sel + HID_USAGE_CONSUMER_AC_SAVE = 0x0207, // Sel + HID_USAGE_CONSUMER_AC_PRINT = 0x0208, // Sel + HID_USAGE_CONSUMER_AC_PROPERTIES = 0x0209, // Sel + // 20A-219 Reserved + HID_USAGE_CONSUMER_AC_UNDO = 0x021A, // Sel + HID_USAGE_CONSUMER_AC_COPY = 0x021B, // Sel + HID_USAGE_CONSUMER_AC_CUT = 0x021C, // Sel + HID_USAGE_CONSUMER_AC_PASTE = 0x021D, // Sel + HID_USAGE_CONSUMER_AC_SELECT_ALL = 0x021E, // Sel + HID_USAGE_CONSUMER_AC_FIND = 0x021F, // Sel + HID_USAGE_CONSUMER_AC_FIND_AND_REPLACE = 0x0220, // Sel + HID_USAGE_CONSUMER_AC_SEARCH = 0x0221, // Sel + HID_USAGE_CONSUMER_AC_GO_TO = 0x0222, // Sel + HID_USAGE_CONSUMER_AC_HOME = 0x0223, // Sel + HID_USAGE_CONSUMER_AC_BACK = 0x0224, // Sel + HID_USAGE_CONSUMER_AC_FORWARD = 0x0225, // Sel + HID_USAGE_CONSUMER_AC_STOP = 0x0226, // Sel + HID_USAGE_CONSUMER_AC_REFRESH = 0x0227, // Sel + HID_USAGE_CONSUMER_AC_PREVIOUS_LINK = 0x0228, // Sel + HID_USAGE_CONSUMER_AC_NEXT_LINK = 0x0229, // Sel + HID_USAGE_CONSUMER_AC_BOOKMARKS = 0x022A, // Sel + HID_USAGE_CONSUMER_AC_HISTORY = 0x022B, // Sel + HID_USAGE_CONSUMER_AC_SUBSCRIPTIONS = 0x022C, // Sel + HID_USAGE_CONSUMER_AC_ZOOM_IN = 0x022D, // Sel + HID_USAGE_CONSUMER_AC_ZOOM_OUT = 0x022E, // Sel + HID_USAGE_CONSUMER_AC_ZOOM = 0x022F, // LC + HID_USAGE_CONSUMER_AC_FULL_SCREEN_VIEW = 0x0230, // Sel + HID_USAGE_CONSUMER_AC_NORMAL_VIEW = 0x0231, // Sel + HID_USAGE_CONSUMER_AC_VIEW_TOGGLE = 0x0232, // Sel + HID_USAGE_CONSUMER_AC_SCROLL_UP = 0x0233, // Sel + HID_USAGE_CONSUMER_AC_SCROLL_DOWN = 0x0234, // Sel + HID_USAGE_CONSUMER_AC_SCROLL = 0x0235, // LC + HID_USAGE_CONSUMER_AC_PAN_LEFT = 0x0236, // Sel + HID_USAGE_CONSUMER_AC_PAN_RIGHT = 0x0237, // Sel + HID_USAGE_CONSUMER_AC_PAN = 0x0238, // LC + HID_USAGE_CONSUMER_AC_NEW_WINDOW = 0x0239, // Sel + HID_USAGE_CONSUMER_AC_TILE_HORIZONTALLY = 0x023A, // Sel + HID_USAGE_CONSUMER_AC_TILE_VERTICALLY = 0x023B, // Sel + HID_USAGE_CONSUMER_AC_FORMAT = 0x023C, // Sel + HID_USAGE_CONSUMER_AC_EDIT = 0x023D, // Sel + HID_USAGE_CONSUMER_AC_BOLD = 0x023E, // Sel + HID_USAGE_CONSUMER_AC_ITALICS = 0x023F, // Sel + HID_USAGE_CONSUMER_AC_UNDERLINE = 0x0240, // Sel + HID_USAGE_CONSUMER_AC_STRIKETHROUGH = 0x0241, // Sel + HID_USAGE_CONSUMER_AC_SUBSCRIPT = 0x0242, // Sel + HID_USAGE_CONSUMER_AC_SUPERSCRIPT = 0x0243, // Sel + HID_USAGE_CONSUMER_AC_ALL_CAPS = 0x0244, // Sel + HID_USAGE_CONSUMER_AC_ROTATE = 0x0245, // Sel + HID_USAGE_CONSUMER_AC_RESIZE = 0x0246, // Sel + HID_USAGE_CONSUMER_AC_FLIP_HORIZONTAL = 0x0247, // Sel + HID_USAGE_CONSUMER_AC_FLIP_VERTICAL = 0x0248, // Sel + HID_USAGE_CONSUMER_AC_MIRROR_HORIZONTAL = 0x0249, // Sel + HID_USAGE_CONSUMER_AC_MIRROR_VERTICAL = 0x024A, // Sel + HID_USAGE_CONSUMER_AC_FONT_SELECT = 0x024B, // Sel + HID_USAGE_CONSUMER_AC_FONT_COLOR = 0x024C, // Sel + HID_USAGE_CONSUMER_AC_FONT_SIZE = 0x024D, // Sel + HID_USAGE_CONSUMER_AC_JUSTIFY_LEFT = 0x024E, // Sel + HID_USAGE_CONSUMER_AC_JUSTIFY_CENTER_H = 0x024F, // Sel + HID_USAGE_CONSUMER_AC_JUSTIFY_RIGHT = 0x0250, // Sel + HID_USAGE_CONSUMER_AC_JUSTIFY_BLOCK_H = 0x0251, // Sel + HID_USAGE_CONSUMER_AC_JUSTIFY_TOP = 0x0252, // Sel + HID_USAGE_CONSUMER_AC_JUSTIFY_CENTER_V = 0x0253, // Sel + HID_USAGE_CONSUMER_AC_JUSTIFY_BOTTOM = 0x0254, // Sel + HID_USAGE_CONSUMER_AC_JUSTIFY_BLOCK_V = 0x0255, // Sel + HID_USAGE_CONSUMER_AC_INDENT_DECREASE = 0x0256, // Sel + HID_USAGE_CONSUMER_AC_INDENT_INCREASE = 0x0257, // Sel + HID_USAGE_CONSUMER_AC_NUMBERED_LIST = 0x0258, // Sel + HID_USAGE_CONSUMER_AC_RESTART_NUMBERING = 0x0259, // Sel + HID_USAGE_CONSUMER_AC_BULLETED_LIST = 0x025A, // Sel + HID_USAGE_CONSUMER_AC_PROMOTE = 0x025B, // Sel + HID_USAGE_CONSUMER_AC_DEMOTE = 0x025C, // Sel + HID_USAGE_CONSUMER_AC_YES = 0x025D, // Sel + HID_USAGE_CONSUMER_AC_NO = 0x025E, // Sel + HID_USAGE_CONSUMER_AC_CANCEL = 0x025F, // Sel + HID_USAGE_CONSUMER_AC_CATALOG = 0x0260, // Sel + HID_USAGE_CONSUMER_AC_BUY_CHECKOUT = 0x0261, // Sel + HID_USAGE_CONSUMER_AC_ADD_TO_CART = 0x0262, // Sel + HID_USAGE_CONSUMER_AC_EXPAND = 0x0263, // Sel + HID_USAGE_CONSUMER_AC_EXPAND_ALL = 0x0264, // Sel + HID_USAGE_CONSUMER_AC_COLLAPSE = 0x0265, // Sel + HID_USAGE_CONSUMER_AC_COLLAPSE_ALL = 0x0266, // Sel + HID_USAGE_CONSUMER_AC_PRINT_PREVIEW = 0x0267, // Sel + HID_USAGE_CONSUMER_AC_PASTE_SPECIAL = 0x0268, // Sel + HID_USAGE_CONSUMER_AC_INSERT_MODE = 0x0269, // Sel + HID_USAGE_CONSUMER_AC_DELETE = 0x026A, // Sel + HID_USAGE_CONSUMER_AC_LOCK = 0x026B, // Sel + HID_USAGE_CONSUMER_AC_UNLOCK = 0x026C, // Sel + HID_USAGE_CONSUMER_AC_PROTECT = 0x026D, // Sel + HID_USAGE_CONSUMER_AC_UNPROTECT = 0x026E, // Sel + HID_USAGE_CONSUMER_AC_ATTACH_COMMENT = 0x026F, // Sel + HID_USAGE_CONSUMER_AC_DELETE_COMMENT = 0x0270, // Sel + HID_USAGE_CONSUMER_AC_VIEW_COMMENT = 0x0271, // Sel + HID_USAGE_CONSUMER_AC_SELECT_WORD = 0x0272, // Sel + HID_USAGE_CONSUMER_AC_SELECT_SENTENCE = 0x0273, // Sel + HID_USAGE_CONSUMER_AC_SELECT_PARAGRAPH = 0x0274, // Sel + HID_USAGE_CONSUMER_AC_SELECT_COLUMN = 0x0275, // Sel + HID_USAGE_CONSUMER_AC_SELECT_ROW = 0x0276, // Sel + HID_USAGE_CONSUMER_AC_SELECT_TABLE = 0x0277, // Sel + HID_USAGE_CONSUMER_AC_SELECT_OBJECT = 0x0278, // Sel + HID_USAGE_CONSUMER_AC_REDO_REPEAT = 0x0279, // Sel + HID_USAGE_CONSUMER_AC_SORT = 0x027A, // Sel + HID_USAGE_CONSUMER_AC_SORT_ASCENDING = 0x027B, // Sel + HID_USAGE_CONSUMER_AC_SORT_DESCENDING = 0x027C, // Sel + HID_USAGE_CONSUMER_AC_FILTER = 0x027D, // Sel + HID_USAGE_CONSUMER_AC_SET_CLOCK = 0x027E, // Sel + HID_USAGE_CONSUMER_AC_VIEW_CLOCK = 0x027F, // Sel + HID_USAGE_CONSUMER_AC_SELECT_TIME_ZONE = 0x0280, // Sel + HID_USAGE_CONSUMER_AC_EDIT_TIME_ZONES = 0x0281, // Sel + HID_USAGE_CONSUMER_AC_SET_ALARM = 0x0282, // Sel + HID_USAGE_CONSUMER_AC_CLEAR_ALARM = 0x0283, // Sel + HID_USAGE_CONSUMER_AC_SNOOZE_ALARM = 0x0284, // Sel + HID_USAGE_CONSUMER_AC_RESET_ALARM = 0x0285, // Sel + HID_USAGE_CONSUMER_AC_SYNCHRONIZE = 0x0286, // Sel + HID_USAGE_CONSUMER_AC_SEND_RECEIVE = 0x0287, // Sel + HID_USAGE_CONSUMER_AC_SEND_TO = 0x0288, // Sel + HID_USAGE_CONSUMER_AC_REPLY = 0x0289, // Sel + HID_USAGE_CONSUMER_AC_REPLY_ALL = 0x028A, // Sel + HID_USAGE_CONSUMER_AC_FORWARD_MSG = 0x028B, // Sel + HID_USAGE_CONSUMER_AC_SEND = 0x028C, // Sel + HID_USAGE_CONSUMER_AC_ATTACH_FILE = 0x028D, // Sel + HID_USAGE_CONSUMER_AC_UPLOAD = 0x028E, // Sel + HID_USAGE_CONSUMER_AC_DOWNLOAD_SAVE_TARGET_AS = 0x028F, // Sel + HID_USAGE_CONSUMER_AC_SET_BORDERS = 0x0290, // Sel + HID_USAGE_CONSUMER_AC_INSERT_ROW = 0x0291, // Sel + HID_USAGE_CONSUMER_AC_INSERT_COLUMN = 0x0292, // Sel + HID_USAGE_CONSUMER_AC_INSERT_FILE = 0x0293, // Sel + HID_USAGE_CONSUMER_AC_INSERT_PICTURE = 0x0294, // Sel + HID_USAGE_CONSUMER_AC_INSERT_OBJECT = 0x0295, // Sel + HID_USAGE_CONSUMER_AC_INSERT_SYMBOL = 0x0296, // Sel + HID_USAGE_CONSUMER_AC_SAVE_AND_CLOSE = 0x0297, // Sel + HID_USAGE_CONSUMER_AC_RENAME = 0x0298, // Sel + HID_USAGE_CONSUMER_AC_MERGE = 0x0299, // Sel + HID_USAGE_CONSUMER_AC_SPLIT = 0x029A, // Sel + HID_USAGE_CONSUMER_AC_DISRIBUTE_HORIZONTALLY = 0x029B, // Sel + HID_USAGE_CONSUMER_AC_DISTRIBUTE_VERTICALLY = 0x029C, // Sel + HID_USAGE_CONSUMER_AC_NEXT_KEYBOARD_LAYOUT_SELECT = 0x029D, // Sel + HID_USAGE_CONSUMER_AC_NAVIGATION_GUIDANCE = 0x029E, // Sel + HID_USAGE_CONSUMER_AC_DESKTOP_SHOW_ALL_WINDOWS = 0x029F, // Sel + HID_USAGE_CONSUMER_AC_SOFT_KEY_LEFT = 0x02A0, // Sel + HID_USAGE_CONSUMER_AC_SOFT_KEY_RIGHT = 0x02A1, // Sel + HID_USAGE_CONSUMER_AC_DESKTOP_SHOW_ALL_APPLICATIONS = 0x02A2, // Sel + // 2A3-2AF Reserved + HID_USAGE_CONSUMER_AC_IDLE_KEEP_ALIVE = 0x02B0, // Sel + // 2B1-2BF Reserved + HID_USAGE_CONSUMER_EXTENDED_KEYBOARD_ATTRIBUTES_COLLECTION = 0x02C0, // CL + HID_USAGE_CONSUMER_KEYBOARD_FORM_FACTOR = 0x02C1, // SV + HID_USAGE_CONSUMER_KEYBOARD_KEY_TYPE = 0x02C2, // SV + HID_USAGE_CONSUMER_KEYBOARD_PHYSICAL_LAYOUT = 0x02C3, // SV + HID_USAGE_CONSUMER_VENDOR_SPECIFIC_KEYBOARD_PHYSICAL_LAYOUT = 0x02C4, // SV + HID_USAGE_CONSUMER_KEYBOARD_IETF_LANGUAGE_TAG_INDEX = 0x02C5, // SV + HID_USAGE_CONSUMER_IMPLEMENTED_KEYBOARD_INPUT_ASSIST_CONTROLS = 0x02C6, // SV + HID_USAGE_CONSUMER_KEYBOARD_INPUT_ASSIST_PREVIOUS = 0x02C7, // Sel + HID_USAGE_CONSUMER_KEYBOARD_INPUT_ASSIST_NEXT = 0x02C8, // Sel + HID_USAGE_CONSUMER_KEYBOARD_INPUT_ASSIST_PREVIOUS_GROUP = 0x02C9, // Sel + HID_USAGE_CONSUMER_KEYBOARD_INPUT_ASSIST_NEXT_GROUP = 0x02CA, // Sel + HID_USAGE_CONSUMER_KEYBOARD_INPUT_ASSIST_ACCEPT = 0x02CB, // Sel + HID_USAGE_CONSUMER_KEYBOARD_INPUT_ASSIST_CANCEL = 0x02CC, // Sel + // 2CD-2CF Reserved + HID_USAGE_CONSUMER_PRIVACY_SCREEN_TOGGLE = 0x02D0, // OOC + HID_USAGE_CONSUMER_PRIVACY_SCREEN_LEVEL_DECREMENT = 0x02D1, // RTC + HID_USAGE_CONSUMER_PRIVACY_SCREEN_LEVEL_INCREMENT = 0x02D2, // RTC + HID_USAGE_CONSUMER_PRIVACY_SCREEN_LEVEL_MINIMUM = 0x02D3, // OSC + HID_USAGE_CONSUMER_PRIVACY_SCREEN_LEVEL_MAXIMUM = 0x02D4, // OSC + // 2D5-4FF Reserved + HID_USAGE_CONSUMER_CONTACT_EDITED = 0x0500, // OOC + HID_USAGE_CONSUMER_CONTACT_ADDED = 0x0501, // OOC + HID_USAGE_CONSUMER_CONTACT_RECORD_ACTIVE = 0x0502, // OOC + HID_USAGE_CONSUMER_CONTACT_INDEX = 0x0503, // DV + HID_USAGE_CONSUMER_CONTACT_NICKNAME = 0x0504, // DV + HID_USAGE_CONSUMER_CONTACT_FIRST_NAME = 0x0505, // DV + HID_USAGE_CONSUMER_CONTACT_LAST_NAME = 0x0506, // DV + HID_USAGE_CONSUMER_CONTACT_FULL_NAME = 0x0507, // DV + HID_USAGE_CONSUMER_CONTACT_PHONE_NUMBER_PERSONAL = 0x0508, // DV + HID_USAGE_CONSUMER_CONTACT_PHONE_NUMBER_BUSINESS = 0x0509, // DV + HID_USAGE_CONSUMER_CONTACT_PHONE_NUMBER_MOBILE = 0x050A, // DV + HID_USAGE_CONSUMER_CONTACT_PHONE_NUMBER_PAGER = 0x050B, // DV + HID_USAGE_CONSUMER_CONTACT_PHONE_NUMBER_FAX = 0x050C, // DV + HID_USAGE_CONSUMER_CONTACT_PHONE_NUMBER_OTHER = 0x050D, // DV + HID_USAGE_CONSUMER_CONTACT_EMAIL_PERSONAL = 0x050E, // DV + HID_USAGE_CONSUMER_CONTACT_EMAIL_BUSINESS = 0x050F, // DV + HID_USAGE_CONSUMER_CONTACT_EMAIL_OTHER = 0x0510, // DV + HID_USAGE_CONSUMER_CONTACT_EMAIL_MAIN = 0x0511, // DV + HID_USAGE_CONSUMER_CONTACT_SPEED_DIAL_NUMBER = 0x0512, // DV + HID_USAGE_CONSUMER_CONTACT_STATUS_FLAG = 0x0513, // DV + HID_USAGE_CONSUMER_CONTACT_MISC = 0x0514, // DV + HID_USAGE_CONSUMER_KEYBOARD_BRIGHTNESS_NEXT = 0x0515, // OSC + HID_USAGE_CONSUMER_KEYBOARD_BRIGHTNESS_PREVIOUS = 0x0516, // OSC + HID_USAGE_CONSUMER_KEYBOARD_BACKLIGHT_LEVEL_SUGGESTION = 0x0517, // SV + // 518-FFFF Reserved }; /// HID Usage Table: Digitizer Page (0x0D) @@ -1371,6 +1931,7 @@ enum { HID_USAGE_DIGITIZER_SURFACE_SWITCH = 0x57, // DF HID_USAGE_DIGITIZER_BUTTON_SWITCH = 0x58, // DF HID_USAGE_DIGITIZER_PAD_TYPE = 0x59, // SF + HID_USAGE_DIGITIZER_SECONDARY_BARREL_SWITCH = 0x5A, // MC HID_USAGE_DIGITIZER_TRANSDUCER_SERIAL_NUMBER = 0x5B, // SV HID_USAGE_DIGITIZER_PREFERRED_COLOR = 0x5C, // DV HID_USAGE_DIGITIZER_PREFERRED_COLOR_LOCKED = 0x5D, // MC @@ -1431,151 +1992,500 @@ enum { // Reserved (0xB1 - 0xFFFF) }; +/// HID Usage Table: Haptics Page (0x0E) +enum{ + HID_USAGE_HAPTICS_SIMPLE_HAPTIC_CONTROLLER = 0x0001, // CA/CL + // Reserved (0x0002 - 0x000F) + HID_USAGE_HAPTICS_WAVEFORM_LIST = 0x0010, // NAry + HID_USAGE_HAPTICS_DURATION_LIST = 0x0011, // NAry + // Reserved (0x0012 - 0x001F) + HID_USAGE_HAPTICS_AUTO_TRIGGER = 0x0020, // DV + HID_USAGE_HAPTICS_MANUAL_TRIGGER = 0x0021, // DV + HID_USAGE_HAPTICS_AUTO_TRIGGER_ASSOCIATED_CONTROL = 0x0022, // SV + HID_USAGE_HAPTICS_INTENSITY = 0x0023, // DV + HID_USAGE_HAPTICS_REPEAT_COUNT = 0x0024, // DV + HID_USAGE_HAPTICS_RETRIGGER_PERIOD = 0x0025, // DV + HID_USAGE_HAPTICS_WAVEFORM_VENDOR_PAGE = 0x0026, // SV + HID_USAGE_HAPTICS_WAVEFORM_VENDOR_ID = 0x0027, // SV + HID_USAGE_HAPTICS_WAVEFORM_CUTOFF_TIME = 0x0028, // SV + // Reserved (0x0029 - 0x1000) + HID_USAGE_HAPTICS_WAVEFORM_NONE = 0x1001, // SV + HID_USAGE_HAPTICS_WAVEFORM_STOP = 0x1002, // SV + HID_USAGE_HAPTICS_WAVEFORM_CLICK = 0x1003, // SV + HID_USAGE_HAPTICS_WAVEFORM_BUZZ_CONTINUOUS = 0x1004, // SV + HID_USAGE_HAPTICS_WAVEFORM_RUMBLE_CONTINUOUS = 0x1005, // SV + HID_USAGE_HAPTICS_WAVEFORM_PRESS = 0x1006, // SV + HID_USAGE_HAPTICS_WAVEFORM_RELEASE = 0x1007, // SV + HID_USAGE_HAPTICS_WAVEFORM_HOVER = 0x1008, // SV + HID_USAGE_HAPTICS_WAVEFORM_SUCCESS = 0x1009, // SV + HID_USAGE_HAPTICS_WAVEFORM_ERROR = 0x100A, // SV + HID_USAGE_HAPTICS_WAVEFORM_INK_CONTINUOUS = 0x100B, // SV + HID_USAGE_HAPTICS_WAVEFORM_PENCIL_CONTINUOUS = 0x100C, // SV + HID_USAGE_HAPTICS_WAVEFORM_MARKER_CONTINUOUS = 0x100D, // SV + HID_USAGE_HAPTICS_WAVEFORM_CHISEL_MARKER_CONTINUOUS = 0x100E, // SV + HID_USAGE_HAPTICS_WAVEFORM_BRUSH_CONTINUOUS = 0x100F, // SV + HID_USAGE_HAPTICS_WAVEFORM_ERASER_CONTINUOUS = 0x1010, // SV + HID_USAGE_HAPTICS_WAVEFORM_SPARKLE_CONTINUOUS = 0x1011, // SV + // Reserved (0x1012 - 0xFFFF) +}; + /// HID Usage Table: Physical Input Device Page (0x0F) enum { HID_USAGE_PID_UNDEFINED = 0x00, - HID_USAGE_PID_PHYSICAL_INPUT_DEVICE = 0x01, - HID_USAGE_PID_NORMAL = 0x20, - HID_USAGE_PID_SET_EFFECT_REPORT = 0x21, - HID_USAGE_PID_EFFECT_PARAMETER_BLOCK_INDEX = 0x22, - HID_USAGE_PID_PARAMETER_BLOCK_OFFSET = 0x23, - HID_USAGE_PID_ROM_FLAG = 0x24, - HID_USAGE_PID_EFFECT_TYPE = 0x25, - HID_USAGE_PID_ET_CONSTANTFORCE = 0x26, - HID_USAGE_PID_ET_RAMP = 0x27, - HID_USAGE_PID_ET_CUSTOMFORCE = 0x28, - HID_USAGE_PID_ET_SQUARE = 0x30, - HID_USAGE_PID_ET_SINE = 0x31, - HID_USAGE_PID_ET_TRIANGLE = 0x32, - HID_USAGE_PID_ET_SAWTOOTH_UP = 0x33, - HID_USAGE_PID_ET_SAWTOOTH_DOWN = 0x34, - HID_USAGE_PID_ET_SPRING = 0x40, - HID_USAGE_PID_ET_DAMPER = 0x41, - HID_USAGE_PID_ET_INERTIA = 0x42, - HID_USAGE_PID_ET_FRICTION = 0x43, - HID_USAGE_PID_DURATION = 0x50, - HID_USAGE_PID_SAMPLE_PERIOD = 0x51, - HID_USAGE_PID_GAIN = 0x52, - HID_USAGE_PID_TRIGGER_BUTTON = 0x53, - HID_USAGE_PID_TRIGGER_REPEAT_INTERVAL = 0x54, - HID_USAGE_PID_AXES_ENABLE = 0x55, - HID_USAGE_PID_DIRECTION_ENABLE = 0x56, - HID_USAGE_PID_DIRECTION = 0x57, - HID_USAGE_PID_TYPE_SPECIFIC_BLOCK_OFFSET = 0x58, - HID_USAGE_PID_BLOCK_TYPE = 0x59, - HID_USAGE_PID_SET_ENVELOPE_REPORT = 0x5a, - HID_USAGE_PID_ATTACK_LEVEL = 0x5b, - HID_USAGE_PID_ATTACK_TIME = 0x5c, - HID_USAGE_PID_FADE_LEVEL = 0x5d, - HID_USAGE_PID_FADE_TIME = 0x5e, - HID_USAGE_PID_SET_CONDITION_REPORT = 0x5f, - HID_USAGE_PID_CENTERPOINT_OFFSET = 0x60, - HID_USAGE_PID_POSITIVE_COEFFICIENT = 0x61, - HID_USAGE_PID_NEGATIVE_COEFFICIENT = 0x62, - HID_USAGE_PID_POSITIVE_SATURATION = 0x63, - HID_USAGE_PID_NEGATIVE_SATURATION = 0x64, - HID_USAGE_PID_DEAD_BAND = 0x65, - HID_USAGE_PID_DOWNLOAD_FORCE_SAMPLE = 0x66, - HID_USAGE_PID_ISOCH_CUSTOMFORCE_ENABLE = 0x67, - HID_USAGE_PID_CUSTOMFORCE_DATA_REPORT = 0x68, - HID_USAGE_PID_CUSTOMFORCE_DATA = 0x69, - HID_USAGE_PID_CUSTOMFORCE_VENDOR_DEFINED_DATA = 0x6a, - HID_USAGE_PID_SET_CUSTOMFORCE_REPORT = 0x6b, - HID_USAGE_PID_CUSTOMFORCE_DATA_OFFSET = 0x6c, - HID_USAGE_PID_SAMPLE_COUNT = 0x6d, - HID_USAGE_PID_SET_PERIODIC_REPORT = 0x6e, - HID_USAGE_PID_OFFSET = 0x6f, - HID_USAGE_PID_MAGNITUDE = 0x70, - HID_USAGE_PID_PHASE = 0x71, - HID_USAGE_PID_PERIOD = 0x72, - HID_USAGE_PID_SET_CONSTANTFORCE_REPORT = 0x73, - HID_USAGE_PID_SET_RAMPFORCE_REPORT = 0x74, - HID_USAGE_PID_RAMP_START = 0x75, - HID_USAGE_PID_RAMP_END = 0x76, - HID_USAGE_PID_EFFECT_OPERATION_REPORT = 0x77, - HID_USAGE_PID_EFFECT_OPERATION = 0x78, - HID_USAGE_PID_OP_EFFECT_START = 0x79, - HID_USAGE_PID_OP_EFFECT_START_SOLO = 0x7a, - HID_USAGE_PID_OP_EFFECT_STOP = 0x7b, - HID_USAGE_PID_LOOP_COUNT = 0x7c, - HID_USAGE_PID_DEVICE_GAIN_REPORT = 0x7d, - HID_USAGE_PID_DEVICE_GAIN = 0x7e, - HID_USAGE_PID_PARAMETER_BLOCK_POOLS_REPORT = 0x7f, - HID_USAGE_PID_RAM_POOL_SIZE = 0x80, - HID_USAGE_PID_ROM_POOL_SIZE = 0x81, - HID_USAGE_PID_ROM_EFFECT_BLOCK_COUNT = 0x82, - HID_USAGE_PID_SIMULTANEOUS_EFFECTS_MAX = 0x83, - HID_USAGE_PID_POOL_ALIGNMENT = 0x84, - HID_USAGE_PID_PARAMETER_BLOCK_MOVE_REPORT = 0x85, - HID_USAGE_PID_MOVE_SOURCE = 0x86, - HID_USAGE_PID_MOVE_DESTINATION = 0x87, - HID_USAGE_PID_MOVE_LENGTH = 0x88, - HID_USAGE_PID_EFFECT_PARAMETER_BLOCK_LOAD_REPORT = 0x89, - HID_USAGE_PID_EFFECT_PARAMETER_BLOCK_LOAD_STATUS = 0x8b, - HID_USAGE_PID_BLOCK_LOAD_SUCCESS = 0x8c, - HID_USAGE_PID_BLOCK_LOAD_FULL = 0x8d, - HID_USAGE_PID_BLOCK_LOAD_ERROR = 0x8e, - HID_USAGE_PID_BLOCK_HANDLE = 0x8f, - HID_USAGE_PID_EFFECT_PARAMETER_BLOCK_FREE_REPORT = 0x90, - HID_USAGE_PID_TYPE_SPECIFIC_BLOCK_HANDLE = 0x91, - HID_USAGE_PID_PID_STATE_REPORT = 0x92, - HID_USAGE_PID_EFFECT_PLAYING = 0x94, - HID_USAGE_PID_PID_DEVICE_CONTROL_REPORT = 0x95, - HID_USAGE_PID_PID_DEVICE_CONTROL = 0x96, - HID_USAGE_PID_DC_ENABLE_ACTUATORS = 0x97, - HID_USAGE_PID_DC_DISABLE_ACTUATORS = 0x98, - HID_USAGE_PID_DC_STOP_ALL_EFFECTS = 0x99, - HID_USAGE_PID_DC_RESET = 0x9a, - HID_USAGE_PID_DC_PAUSE = 0x9b, - HID_USAGE_PID_DC_CONTINUE = 0x9c, - HID_USAGE_PID_DEVICE_PAUSED = 0x9f, - HID_USAGE_PID_ACTUATORS_ENABLED = 0xa0, - HID_USAGE_PID_SAFETY_SWITCH = 0xa4, - HID_USAGE_PID_ACTUATOR_OVERRIDE_SWITCH = 0xa5, - HID_USAGE_PID_ACTUATOR_POWER = 0xa6, - HID_USAGE_PID_START_DELAY = 0xa7, - HID_USAGE_PID_PARAMETER_BLOCK_SIZE = 0xa8, - HID_USAGE_PID_DEVICEMANAGED_POOL = 0xa9, - HID_USAGE_PID_SHARED_PARAMETER_BLOCKS = 0xaa, - HID_USAGE_PID_CREATE_NEW_EFFECT_PARAMETER_BLOCK_REPORT = 0xab, - HID_USAGE_PID_RAM_POOL_AVAILABLE = 0xac, + HID_USAGE_PID_PHYSICAL_INPUT_DEVICE = 0x01, // CA + // Reserved (0x02 - 0x1F) + HID_USAGE_PID_NORMAL = 0x20, // DV + HID_USAGE_PID_SET_EFFECT_REPORT = 0x21, // CL + HID_USAGE_PID_EFFECT_PARAMETER_BLOCK_INDEX = 0x22, // DV + HID_USAGE_PID_PARAMETER_BLOCK_OFFSET = 0x23, // DV + HID_USAGE_PID_ROM_FLAG = 0x24, // DF + HID_USAGE_PID_EFFECT_TYPE = 0x25, // NAry + HID_USAGE_PID_ET_CONSTANTFORCE = 0x26, // Sel + HID_USAGE_PID_ET_RAMP = 0x27, // Sel + HID_USAGE_PID_ET_CUSTOMFORCE = 0x28, // Sel + // Reserved (0x29 - 0x2F) + HID_USAGE_PID_ET_SQUARE = 0x30, // Sel + HID_USAGE_PID_ET_SINE = 0x31, // Sel + HID_USAGE_PID_ET_TRIANGLE = 0x32, // Sel + HID_USAGE_PID_ET_SAWTOOTH_UP = 0x33, // Sel + HID_USAGE_PID_ET_SAWTOOTH_DOWN = 0x34, // Sel + // Reserved (0x35 - 0x3F) + HID_USAGE_PID_ET_SPRING = 0x40, // Sel + HID_USAGE_PID_ET_DAMPER = 0x41, // Sel + HID_USAGE_PID_ET_INERTIA = 0x42, // Sel + HID_USAGE_PID_ET_FRICTION = 0x43, // Sel + // Reserved (0x44 - 0x4F) + HID_USAGE_PID_DURATION = 0x50, // DV + HID_USAGE_PID_SAMPLE_PERIOD = 0x51, // DV + HID_USAGE_PID_GAIN = 0x52, // DV + HID_USAGE_PID_TRIGGER_BUTTON = 0x53, // DV + HID_USAGE_PID_TRIGGER_REPEAT_INTERVAL = 0x54, // DV + HID_USAGE_PID_AXES_ENABLE = 0x55, // US + HID_USAGE_PID_DIRECTION_ENABLE = 0x56, // DF + HID_USAGE_PID_DIRECTION = 0x57, // CL + HID_USAGE_PID_TYPE_SPECIFIC_BLOCK_OFFSET = 0x58, // CL + HID_USAGE_PID_BLOCK_TYPE = 0x59, // NAry + HID_USAGE_PID_SET_ENVELOPE_REPORT = 0x5A, // CL/SV + HID_USAGE_PID_ATTACK_LEVEL = 0x5B, // DV + HID_USAGE_PID_ATTACK_TIME = 0x5C, // DV + HID_USAGE_PID_FADE_LEVEL = 0x5D, // DV + HID_USAGE_PID_FADE_TIME = 0x5E, // DV + HID_USAGE_PID_SET_CONDITION_REPORT = 0x5F, // CL/SV + HID_USAGE_PID_CENTERPOINT_OFFSET = 0x60, // DV + HID_USAGE_PID_POSITIVE_COEFFICIENT = 0x61, // DV + HID_USAGE_PID_NEGATIVE_COEFFICIENT = 0x62, // DV + HID_USAGE_PID_POSITIVE_SATURATION = 0x63, // DV + HID_USAGE_PID_NEGATIVE_SATURATION = 0x64, // DV + HID_USAGE_PID_DEAD_BAND = 0x65, // DV + HID_USAGE_PID_DOWNLOAD_FORCE_SAMPLE = 0x66, // CL + HID_USAGE_PID_ISOCH_CUSTOMFORCE_ENABLE = 0x67, // DF + HID_USAGE_PID_CUSTOMFORCE_DATA_REPORT = 0x68, // CL + HID_USAGE_PID_CUSTOMFORCE_DATA = 0x69, // DV + HID_USAGE_PID_CUSTOMFORCE_VENDOR_DEFINED_DATA = 0x6A, // DV + HID_USAGE_PID_SET_CUSTOMFORCE_REPORT = 0x6B, // CL/SV + HID_USAGE_PID_CUSTOMFORCE_DATA_OFFSET = 0x6C, // DV + HID_USAGE_PID_SAMPLE_COUNT = 0x6D, // DV + HID_USAGE_PID_SET_PERIODIC_REPORT = 0x6E, // CL/SV + HID_USAGE_PID_OFFSET = 0x6F, // DV + HID_USAGE_PID_MAGNITUDE = 0x70, // DV + HID_USAGE_PID_PHASE = 0x71, // DV + HID_USAGE_PID_PERIOD = 0x72, // DV + HID_USAGE_PID_SET_CONSTANTFORCE_REPORT = 0x73, // CL/SV + HID_USAGE_PID_SET_RAMPFORCE_REPORT = 0x74, // CL/SV + HID_USAGE_PID_RAMP_START = 0x75, // DV + HID_USAGE_PID_RAMP_END = 0x76, // DV + HID_USAGE_PID_EFFECT_OPERATION_REPORT = 0x77, // CL + HID_USAGE_PID_EFFECT_OPERATION = 0x78, // NAry + HID_USAGE_PID_OP_EFFECT_START = 0x79, // Sel + HID_USAGE_PID_OP_EFFECT_START_SOLO = 0x7A, // Sel + HID_USAGE_PID_OP_EFFECT_STOP = 0x7B, // Sel + HID_USAGE_PID_LOOP_COUNT = 0x7C, // DV + HID_USAGE_PID_DEVICE_GAIN_REPORT = 0x7D, // CL + HID_USAGE_PID_DEVICE_GAIN = 0x7E, // DV + HID_USAGE_PID_PARAMETER_BLOCK_POOLS_REPORT = 0x7F, // CL + HID_USAGE_PID_RAM_POOL_SIZE = 0x80, // DV + HID_USAGE_PID_ROM_POOL_SIZE = 0x81, // SV + HID_USAGE_PID_ROM_EFFECT_BLOCK_COUNT = 0x82, // SV + HID_USAGE_PID_SIMULTANEOUS_EFFECTS_MAX = 0x83, // SV + HID_USAGE_PID_POOL_ALIGNMENT = 0x84, // SV + HID_USAGE_PID_PARAMETER_BLOCK_MOVE_REPORT = 0x85, // CL + HID_USAGE_PID_MOVE_SOURCE = 0x86, // DV + HID_USAGE_PID_MOVE_DESTINATION = 0x87, // DV + HID_USAGE_PID_MOVE_LENGTH = 0x88, // DV + HID_USAGE_PID_EFFECT_PARAMETER_BLOCK_LOAD_REPORT = 0x89, // CL + // Reserved (0x8A) + HID_USAGE_PID_EFFECT_PARAMETER_BLOCK_LOAD_STATUS = 0x8B, // NAry + HID_USAGE_PID_BLOCK_LOAD_SUCCESS = 0x8C, // Sel + HID_USAGE_PID_BLOCK_LOAD_FULL = 0x8D, // Sel + HID_USAGE_PID_BLOCK_LOAD_ERROR = 0x8E, // Sel + HID_USAGE_PID_BLOCK_HANDLE = 0x8F, // DV + HID_USAGE_PID_EFFECT_PARAMETER_BLOCK_FREE_REPORT = 0x90, // CL + HID_USAGE_PID_TYPE_SPECIFIC_BLOCK_HANDLE = 0x91, // CL + HID_USAGE_PID_PID_STATE_REPORT = 0x92, // CL + // Reserved (0x93) + HID_USAGE_PID_EFFECT_PLAYING = 0x94, // DF + HID_USAGE_PID_PID_DEVICE_CONTROL_REPORT = 0x95, // CL + HID_USAGE_PID_PID_DEVICE_CONTROL = 0x96, // NAry + HID_USAGE_PID_DC_ENABLE_ACTUATORS = 0x97, // Sel + HID_USAGE_PID_DC_DISABLE_ACTUATORS = 0x98, // Sel + HID_USAGE_PID_DC_STOP_ALL_EFFECTS = 0x99, // Sel + HID_USAGE_PID_DC_RESET = 0x9A, // Sel + HID_USAGE_PID_DC_PAUSE = 0x9B, // Sel + HID_USAGE_PID_DC_CONTINUE = 0x9C, // Sel + // Reserved (0x9D - 0x9E) + HID_USAGE_PID_DEVICE_PAUSED = 0x9F, // DF + HID_USAGE_PID_ACTUATORS_ENABLED = 0xA0, // DF + // Reserved (0xA1 - 0xA3) + HID_USAGE_PID_SAFETY_SWITCH = 0xA4, // DF + HID_USAGE_PID_ACTUATOR_OVERRIDE_SWITCH = 0xA5, // DF + HID_USAGE_PID_ACTUATOR_POWER = 0xA6, // OOC + HID_USAGE_PID_START_DELAY = 0xA7, // DV + HID_USAGE_PID_PARAMETER_BLOCK_SIZE = 0xA8, // CL + HID_USAGE_PID_DEVICEMANAGED_POOL = 0xA9, // SF + HID_USAGE_PID_SHARED_PARAMETER_BLOCKS = 0xAA, // SF + HID_USAGE_PID_CREATE_NEW_EFFECT_PARAMETER_BLOCK_REPORT = 0xAB, // CL + HID_USAGE_PID_RAM_POOL_AVAILABLE = 0xAC, // DV + // Reserved (0xAD - 0xFFFF) +}; + +/// HID Usage Table: Unicode Page (0x10) +/// Intentionally skipped + +/// HID Usage Table: SoC Page (0x11) +enum { + HID_USAGE_SOC_SOC_CONTROL = 0x01, // CA + HID_USAGE_SOC_FIRMWARE_TRANSFER = 0x02, // CL + HID_USAGE_SOC_FIRMWARE_FILE_ID = 0x03, // DV + HID_USAGE_SOC_FILE_OFFSET_IN_BYTES = 0x04, // DV + HID_USAGE_SOC_FILE_TRANSFER_SIZE_MAX_IN_BYTES = 0x05, // DV + HID_USAGE_SOC_FILE_PAYLOAD = 0x06, // DV + HID_USAGE_SOC_FILE_PAYLOAD_SIZE_IN_BYTES = 0x07, // DV + HID_USAGE_SOC_FILE_PAYLOAD_CONTAINS_LAST_BYTES = 0x08, // DF + HID_USAGE_SOC_FILE_TRANSFER_STOP = 0x09, // DF + HID_USAGE_SOC_FILE_TRANSFER_TILL_END = 0x0A // DF + // Reserved (0x0B - 0xFFFF) +}; + +/// HID Usage Table: Eye and Head Trackers Page (0x12) +enum { + HID_USAGE_EYE_AND_HEAD_TRACKER_EYE_TRACKER = 0x0001, // CA + HID_USAGE_EYE_AND_HEAD_TRACKER_HEAD_TRACKER = 0x0002, // CA + // Reserved (0x0003 - 0x000F) + HID_USAGE_EYE_AND_HEAD_TRACKER_TRACKING_DATA = 0x0010, // CP + HID_USAGE_EYE_AND_HEAD_TRACKER_CAPABILITIES = 0x0011, // CL + HID_USAGE_EYE_AND_HEAD_TRACKER_CONFIGURATION = 0x0012, // CL + HID_USAGE_EYE_AND_HEAD_TRACKER_STATUS = 0x0013, // CL + HID_USAGE_EYE_AND_HEAD_TRACKER_CONTROL = 0x0014, // CL + // Reserved (0x0015 - 0x001F) + HID_USAGE_EYE_AND_HEAD_TRACKER_SENSOR_TIMESTAMP = 0x0020, // DV + HID_USAGE_EYE_AND_HEAD_TRACKER_POSITION_X = 0x0021, // DV + HID_USAGE_EYE_AND_HEAD_TRACKER_POSITION_Y = 0x0022, // DV + HID_USAGE_EYE_AND_HEAD_TRACKER_POSITION_Z = 0x0023, // DV + HID_USAGE_EYE_AND_HEAD_TRACKER_GAZE_POINT = 0x0024, // CP + HID_USAGE_EYE_AND_HEAD_TRACKER_LEFT_EYE_POSITION = 0x0025, // CP + HID_USAGE_EYE_AND_HEAD_TRACKER_RIGHT_EYE_POSITION = 0x0026, // CP + HID_USAGE_EYE_AND_HEAD_TRACKER_HEAD_POSITION = 0x0027, // CP + HID_USAGE_EYE_AND_HEAD_TRACKER_HEAD_DIRECTION_POINT = 0x0028, // CP + HID_USAGE_EYE_AND_HEAD_TRACKER_ROTATION_ABOUT_X_AXIS = 0x0029, // DV + HID_USAGE_EYE_AND_HEAD_TRACKER_ROTATION_ABOUT_Y_AXIS = 0x002A, // DV + HID_USAGE_EYE_AND_HEAD_TRACKER_ROTATION_ABOUT_Z_AXIS = 0x002B, // DV + // Reserved (0x002C - 0x00FF) + HID_USAGE_EYE_AND_HEAD_TRACKER_TRACKER_QUALITY = 0x0100, // SV + HID_USAGE_EYE_AND_HEAD_TRACKER_MINIMUM_TRACKING_DISTANCE = 0x0101, // SV + HID_USAGE_EYE_AND_HEAD_TRACKER_OPTIMUM_TRACKING_DISTANCE = 0x0102, // SV + HID_USAGE_EYE_AND_HEAD_TRACKER_MAXIMUM_TRACKING_DISTANCE = 0x0103, // SV + HID_USAGE_EYE_AND_HEAD_TRACKER_MAXIMUM_SCREEN_PLANE_WIDTH = 0x0104, // SV + HID_USAGE_EYE_AND_HEAD_TRACKER_MAXIMUM_SCREEN_PLANE_HEIGHT = 0x0105, // SV + // Reserved (0x0106 - 0x01FF) + HID_USAGE_EYE_AND_HEAD_TRACKER_DISPLAY_MANUFACTURER_ID = 0x0200, // SV + HID_USAGE_EYE_AND_HEAD_TRACKER_DISPLAY_PRODUCT_ID = 0x0201, // SV + HID_USAGE_EYE_AND_HEAD_TRACKER_DISPLAY_SERIAL_NUMBER = 0x0202, // SV + HID_USAGE_EYE_AND_HEAD_TRACKER_DISPLAY_MANUFACTURER_DATE = 0x0203, // SV + HID_USAGE_EYE_AND_HEAD_TRACKER_CALIBRATED_SCREEN_WIDTH = 0x0204, // SV + HID_USAGE_EYE_AND_HEAD_TRACKER_CALIBRATED_SCREEN_HEIGHT = 0x0205, // SV + // Reserved (0x0206 - 0x02FF) + HID_USAGE_EYE_AND_HEAD_TRACKER_SAMPLING_FREQUENCY = 0x0300, // DV + HID_USAGE_EYE_AND_HEAD_TRACKER_CONFIGURATION_STATUS = 0x0301, // DV + // Reserved (0x0302 - 0x03FF) + HID_USAGE_EYE_AND_HEAD_TRACKER_DEVICE_MODE_REQUEST = 0x0400, // DV + // Reserved (0x0401 - 0xFFFF) +}; + +/// HID Usage Table - Auxiliary Display Page (0x14) +enum { + HID_USAGE_AUX_DISPLAY_ALPHANUMERIC_DISPLAY = 0x01, // CA + HID_USAGE_AUX_DISPLAY_AUXILIARY_DISPLAY = 0x02, // CA + // Reserved (0x03 - 0x1F) + HID_USAGE_AUX_DISPLAY_DISPLAY_ATTRIBUTES_REPORT = 0x20, // CL + HID_USAGE_AUX_DISPLAY_ASCII_CHARACTER_SET = 0x21, // SF + HID_USAGE_AUX_DISPLAY_DATA_READ_BACK = 0x22, // SF + HID_USAGE_AUX_DISPLAY_FONT_READ_BACK = 0x23, // SF + HID_USAGE_AUX_DISPLAY_DISPLAY_CONTROL_REPORT = 0x24, // CL + HID_USAGE_AUX_DISPLAY_CLEAR_DISPLAY = 0x25, // DF + HID_USAGE_AUX_DISPLAY_DISPLAY_ENABLE = 0x26, // DF + HID_USAGE_AUX_DISPLAY_SCREEN_SAVER_DELAY = 0x27, // SV/DV + HID_USAGE_AUX_DISPLAY_SCREEN_SAVER_ENABLE = 0x28, // DF + HID_USAGE_AUX_DISPLAY_VERTICAL_SCROLL = 0x29, // SF/DF + HID_USAGE_AUX_DISPLAY_HORIZONTAL_SCROLL = 0x2A, // SF/DF + HID_USAGE_AUX_DISPLAY_CHARACTER_REPORT = 0x2B, // CL + HID_USAGE_AUX_DISPLAY_DISPLAY_DATA = 0x2C, // DV + HID_USAGE_AUX_DISPLAY_DISPLAY_STATUS = 0x2D, // CL + HID_USAGE_AUX_DISPLAY_STAT_NOT_READY = 0x2E, // Sel + HID_USAGE_AUX_DISPLAY_STAT_READY = 0x2F, // Sel + HID_USAGE_AUX_DISPLAY_ERR_NOT_A_LOADABLE_CHARACTER = 0x30, // Sel + HID_USAGE_AUX_DISPLAY_ERR_FONT_DATA_CANNOT_BE_READ = 0x31, // Sel + HID_USAGE_AUX_DISPLAY_CURSOR_POSITION_REPORT = 0x32, // Sel + HID_USAGE_AUX_DISPLAY_ROW = 0x33, // DV + HID_USAGE_AUX_DISPLAY_COLUMN = 0x34, // DV + HID_USAGE_AUX_DISPLAY_ROWS = 0x35, // SV + HID_USAGE_AUX_DISPLAY_COLUMNS = 0x36, // SV + HID_USAGE_AUX_DISPLAY_CURSOR_PIXEL_POSITIONING = 0x37, // SF + HID_USAGE_AUX_DISPLAY_CURSOR_MODE = 0x38, // DF + HID_USAGE_AUX_DISPLAY_CURSOR_ENABLE = 0x39, // DF + HID_USAGE_AUX_DISPLAY_CURSOR_BLINK = 0x3A, // DF + HID_USAGE_AUX_DISPLAY_FONT_REPORT = 0x3B, // CL + HID_USAGE_AUX_DISPLAY_FONT_DATA = 0x3C, // Buffered Bytes + HID_USAGE_AUX_DISPLAY_CHARACTER_WIDTH = 0x3D, // SV + HID_USAGE_AUX_DISPLAY_CHARACTER_HEIGHT = 0x3E, // SV + HID_USAGE_AUX_DISPLAY_CHARACTER_SPACING_HORIZONTAL = 0x3F, // SV + HID_USAGE_AUX_DISPLAY_CHARACTER_SPACING_VERTICAL = 0x40, // SV + HID_USAGE_AUX_DISPLAY_UNICODE_CHARACTER_SET = 0x41, // SF + HID_USAGE_AUX_DISPLAY_FONT_7_SEGMENT = 0x42, // SF + HID_USAGE_AUX_DISPLAY_7_SEGMENT_DIRECT_MAP = 0x43, // SF + HID_USAGE_AUX_DISPLAY_FONT_14_SEGMENT = 0x44, // SF + HID_USAGE_AUX_DISPLAY_14_SEGMENT_DIRECT_MAP = 0x45, // SF + HID_USAGE_AUX_DISPLAY_DISPLAY_BRIGHTNESS = 0x46, // DV + HID_USAGE_AUX_DISPLAY_DISPLAY_CONTRAST = 0x47, // DV + HID_USAGE_AUX_DISPLAY_CHARACTER_ATTRIBUTE = 0x48, // CL + HID_USAGE_AUX_DISPLAY_ATTRIBUTE_READBACK = 0x49, // SF + HID_USAGE_AUX_DISPLAY_ATTRIBUTE_DATA = 0x4A, // DV + HID_USAGE_AUX_DISPLAY_CHAR_ATTR_ENHANCE = 0x4B, // OOC + HID_USAGE_AUX_DISPLAY_CHAR_ATTR_UNDERLINE = 0x4C, // OOC + HID_USAGE_AUX_DISPLAY_CHAR_ATTR_BLINK = 0x4D, // OOC + // Reserved (0x4E - 0x7F) + HID_USAGE_AUX_DISPLAY_BITMAP_SIZE_X = 0x80, // SV + HID_USAGE_AUX_DISPLAY_BITMAP_SIZE_Y = 0x81, // SV + HID_USAGE_AUX_DISPLAY_MAX_BLIT_SIZE = 0x82, // SV + HID_USAGE_AUX_DISPLAY_BIT_DEPTH_FORMAT = 0x83, // SV + HID_USAGE_AUX_DISPLAY_DISPLAY_ORIENTATION = 0x84, // DV + HID_USAGE_AUX_DISPLAY_PALETTE_REPORT = 0x85, // CL + HID_USAGE_AUX_DISPLAY_PALETTE_DATA_SIZE = 0x86, // SV + HID_USAGE_AUX_DISPLAY_PALETTE_DATA_OFFSET = 0x87, // SV + HID_USAGE_AUX_DISPLAY_PALETTE_DATA = 0x88, // Buffered Bytes + // Reserved (0x89) + HID_USAGE_AUX_DISPLAY_BLIT_REPORT = 0x8A, // CL + HID_USAGE_AUX_DISPLAY_BLIT_RECTANGLE_X1 = 0x8B, // SV + HID_USAGE_AUX_DISPLAY_BLIT_RECTANGLE_Y1 = 0x8C, // SV + HID_USAGE_AUX_DISPLAY_BLIT_RECTANGLE_X2 = 0x8D, // SV + HID_USAGE_AUX_DISPLAY_BLIT_RECTANGLE_Y2 = 0x8E, // SV + HID_USAGE_AUX_DISPLAY_BLIT_DATA = 0x8F, // Buffered Bytes + HID_USAGE_AUX_DISPLAY_SOFT_BUTTON = 0x90, // CL + HID_USAGE_AUX_DISPLAY_SOFT_BUTTON_ID = 0x91, // SV + HID_USAGE_AUX_DISPLAY_SOFT_BUTTON_SIDE = 0x92, // SV + HID_USAGE_AUX_DISPLAY_SOFT_BUTTON_OFFSET_1 = 0x93, // SV + HID_USAGE_AUX_DISPLAY_SOFT_BUTTON_OFFSET_2 = 0x94, // SV + HID_USAGE_AUX_DISPLAY_SOFT_BUTTON_REPORT = 0x95, // SV + // Reserved (0x96 - 0xC1) + HID_USAGE_AUX_DISPLAY_SOFT_KEYS = 0xC2, // SV + // Reserved (0xC3 - 0xCB) + HID_USAGE_AUX_DISPLAY_DATA_EXTENSIONS = 0xCC, // SF + // Reserved (0xCD - 0xCE) + HID_USAGE_AUX_DISPLAY_CHARACTER_MAPPING = 0xCF, // SV + // Reserved (0xD0 - 0xDC) + HID_USAGE_AUX_DISPLAY_UNICODE_EQUIVALENT = 0xDD, // SV + // Reserved (0xDE) + HID_USAGE_AUX_DISPLAY_CHARACTER_PAGE_MAPPING = 0xDF, // SV + // Reserved (0xE0 - 0xFE) + HID_USAGE_AUX_DISPLAY_REQUEST_REPORT = 0xFF // DV + // Reserved (0x100 - 0xFFFF) +}; + +/// HID Usage Table - Medical Instrument Page (0x40) +enum { + HID_USAGE_MEDICAL_INSTRUMENT_MEDICAL_ULTRASOUND = 0x01, // CA + // Reserved (0x02 - 0x1F) + HID_USAGE_MEDICAL_INSTRUMENT_VCR_ACQUISITION = 0x20, // OOC + HID_USAGE_MEDICAL_INSTRUMENT_FREEZE_THAW = 0x21, // OOC + HID_USAGE_MEDICAL_INSTRUMENT_CLIP_STORE = 0x22, // OSC + HID_USAGE_MEDICAL_INSTRUMENT_UPDATE = 0x23, // OSC + HID_USAGE_MEDICAL_INSTRUMENT_NEXT = 0x24, // OSC + HID_USAGE_MEDICAL_INSTRUMENT_SAVE = 0x25, // OSC + HID_USAGE_MEDICAL_INSTRUMENT_PRINT = 0x26, // OSC + HID_USAGE_MEDICAL_INSTRUMENT_MICROPHONE_ENABLE = 0x27, // OSC + // Reserved (0x28 - 0x3F) + HID_USAGE_MEDICAL_INSTRUMENT_CINE = 0x40, // LC + HID_USAGE_MEDICAL_INSTRUMENT_TRANSMIT_POWER = 0x41, // LC + HID_USAGE_MEDICAL_INSTRUMENT_VOLUME = 0x42, // LC + HID_USAGE_MEDICAL_INSTRUMENT_FOCUS = 0x43, // LC + HID_USAGE_MEDICAL_INSTRUMENT_DEPTH = 0x44, // LC + // Reserved (0x45 - 0x5F) + HID_USAGE_MEDICAL_INSTRUMENT_SOFT_STEP_PRIMARY = 0x60, // LC + HID_USAGE_MEDICAL_INSTRUMENT_SOFT_STEP_SECONDARY = 0x61, // LC + // Reserved (0x62 - 0x6F) + HID_USAGE_MEDICAL_INSTRUMENT_DEPTH_GAIN_COMPENSATION = 0x70, // LC + // Reserved (0x71 - 0x7F) + HID_USAGE_MEDICAL_INSTRUMENT_ZOOM_SELECT = 0x80, // OSC + HID_USAGE_MEDICAL_INSTRUMENT_ZOOM_ADJUST = 0x81, // LC + HID_USAGE_MEDICAL_INSTRUMENT_SPECTRAL_DOPPLER_MODE_SELECT = 0x82, // OSC + HID_USAGE_MEDICAL_INSTRUMENT_SPECTRAL_DOPPLER_ADJUST = 0x83, // LC + HID_USAGE_MEDICAL_INSTRUMENT_COLOR_DOPPLER_MODE_SELECT = 0x84, // OSC + HID_USAGE_MEDICAL_INSTRUMENT_COLOR_DOPPLER_ADJUST = 0x85, // LC + HID_USAGE_MEDICAL_INSTRUMENT_MOTION_MODE_SELECT = 0x86, // OSC + HID_USAGE_MEDICAL_INSTRUMENT_MOTION_MODE_ADJUST = 0x87, // LC + HID_USAGE_MEDICAL_INSTRUMENT_2D_MODE_SELECT = 0x88, // OSC + HID_USAGE_MEDICAL_INSTRUMENT_2D_MODE_ADJUST = 0x89, // LC + // Reserved (0x8A - 0x9F) + HID_USAGE_MEDICAL_INSTRUMENT_SOFT_CONTROL_SELECT = 0xA0, // OSC + HID_USAGE_MEDICAL_INSTRUMENT_SOFT_CONTROL_ADJUST = 0xA1, // LC + // Reserved (0xA2 - 0xFFFF) }; /// HID Usage Table - Lighting And Illumination Page (0x59) enum { - HID_USAGE_LIGHTING_LAMP_ARRAY = 0x01, - HID_USAGE_LIGHTING_LAMP_ARRAY_ATTRIBUTES_REPORT = 0x02, - HID_USAGE_LIGHTING_LAMP_COUNT = 0x03, - HID_USAGE_LIGHTING_BOUNDING_BOX_WIDTH_IN_MICROMETERS = 0x04, - HID_USAGE_LIGHTING_BOUNDING_BOX_HEIGHT_IN_MICROMETERS = 0x05, - HID_USAGE_LIGHTING_BOUNDING_BOX_DEPTH_IN_MICROMETERS = 0x06, - HID_USAGE_LIGHTING_LAMP_ARRAY_KIND = 0x07, - HID_USAGE_LIGHTING_MIN_UPDATE_INTERVAL_IN_MICROSECONDS = 0x08, - HID_USAGE_LIGHTING_LAMP_ATTRIBUTES_REQUEST_REPORT = 0x20, - HID_USAGE_LIGHTING_LAMP_ID = 0x21, - HID_USAGE_LIGHTING_LAMP_ATTRIBUTES_RESPONSE_REPORT = 0x22, - HID_USAGE_LIGHTING_POSITION_X_IN_MICROMETERS = 0x23, - HID_USAGE_LIGHTING_POSITION_Y_IN_MICROMETERS = 0x24, - HID_USAGE_LIGHTING_POSITION_Z_IN_MICROMETERS = 0x25, - HID_USAGE_LIGHTING_LAMP_PURPOSES = 0x26, - HID_USAGE_LIGHTING_UPDATE_LATENCY_IN_MICROSECONDS = 0x27, - HID_USAGE_LIGHTING_RED_LEVEL_COUNT = 0x28, - HID_USAGE_LIGHTING_GREEN_LEVEL_COUNT = 0x29, - HID_USAGE_LIGHTING_BLUE_LEVEL_COUNT = 0x2A, - HID_USAGE_LIGHTING_INTENSITY_LEVEL_COUNT = 0x2B, - HID_USAGE_LIGHTING_IS_PROGRAMMABLE = 0x2C, - HID_USAGE_LIGHTING_INPUT_BINDING = 0x2D, - HID_USAGE_LIGHTING_LAMP_MULTI_UPDATE_REPORT = 0x50, - HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL = 0x51, - HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL = 0x52, - HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL = 0x53, - HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL = 0x54, - HID_USAGE_LIGHTING_LAMP_UPDATE_FLAGS = 0x55, - HID_USAGE_LIGHTING_LAMP_RANGE_UPDATE_REPORT = 0x60, - HID_USAGE_LIGHTING_LAMP_ID_START = 0x61, - HID_USAGE_LIGHTING_LAMP_ID_END = 0x62, - HID_USAGE_LIGHTING_LAMP_ARRAY_CONTROL_REPORT = 0x70, - HID_USAGE_LIGHTING_AUTONOMOUS_MODE = 0x71, + HID_USAGE_LIGHTING_LAMP_ARRAY = 0x01, // CA + HID_USAGE_LIGHTING_LAMP_ARRAY_ATTRIBUTES_REPORT = 0x02, // CL + HID_USAGE_LIGHTING_LAMP_COUNT = 0x03, // SV/DV + HID_USAGE_LIGHTING_BOUNDING_BOX_WIDTH_IN_MICROMETERS = 0x04, // SV + HID_USAGE_LIGHTING_BOUNDING_BOX_HEIGHT_IN_MICROMETERS = 0x05, // SV + HID_USAGE_LIGHTING_BOUNDING_BOX_DEPTH_IN_MICROMETERS = 0x06, // SV + HID_USAGE_LIGHTING_LAMP_ARRAY_KIND = 0x07, // SV + HID_USAGE_LIGHTING_MIN_UPDATE_INTERVAL_IN_MICROSECONDS = 0x08, // SV + // Reserved (0x09 - 0x1F) + HID_USAGE_LIGHTING_LAMP_ATTRIBUTES_REQUEST_REPORT = 0x20, // CL + HID_USAGE_LIGHTING_LAMP_ID = 0x21, // SV/DV + HID_USAGE_LIGHTING_LAMP_ATTRIBUTES_RESPONSE_REPORT = 0x22, // CL + HID_USAGE_LIGHTING_POSITION_X_IN_MICROMETERS = 0x23, // DV + HID_USAGE_LIGHTING_POSITION_Y_IN_MICROMETERS = 0x24, // DV + HID_USAGE_LIGHTING_POSITION_Z_IN_MICROMETERS = 0x25, // DV + HID_USAGE_LIGHTING_LAMP_PURPOSES = 0x26, // DV + HID_USAGE_LIGHTING_UPDATE_LATENCY_IN_MICROSECONDS = 0x27, // DV + HID_USAGE_LIGHTING_RED_LEVEL_COUNT = 0x28, // DV + HID_USAGE_LIGHTING_GREEN_LEVEL_COUNT = 0x29, // DV + HID_USAGE_LIGHTING_BLUE_LEVEL_COUNT = 0x2A, // DV + HID_USAGE_LIGHTING_INTENSITY_LEVEL_COUNT = 0x2B, // DV + HID_USAGE_LIGHTING_IS_PROGRAMMABLE = 0x2C, // DV + HID_USAGE_LIGHTING_INPUT_BINDING = 0x2D, // DV + // Reserved (0x2E - 0x4F) + HID_USAGE_LIGHTING_LAMP_MULTI_UPDATE_REPORT = 0x50, // CL + HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL = 0x51, // DV + HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL = 0x52, // DV + HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL = 0x53, // DV + HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL = 0x54, // DV + HID_USAGE_LIGHTING_LAMP_UPDATE_FLAGS = 0x55, // DV + // Reserved (0x56 - 0x5F) + HID_USAGE_LIGHTING_LAMP_RANGE_UPDATE_REPORT = 0x60, // CL + HID_USAGE_LIGHTING_LAMP_ID_START = 0x61, // DV + HID_USAGE_LIGHTING_LAMP_ID_END = 0x62, // DV + // Reserved (0x63 - 0x6F) + HID_USAGE_LIGHTING_LAMP_ARRAY_CONTROL_REPORT = 0x70, // CL + HID_USAGE_LIGHTING_AUTONOMOUS_MODE = 0x71, // DV + // Reserved (0x72 - 0xFFFF) +}; + +/// HID Usage Table: Monitor Page (0x80) +enum { + HID_USAGE_MONITOR_MONITOR_CONTROL = 0x01, // CA + HID_USAGE_MONITOR_EDID_INFORMATION = 0x02, // SV + HID_USAGE_MONITOR_VDIF_INFORMATION = 0x03, // SV + HID_USAGE_MONITOR_VESA_VERSION = 0x04 // SV + // Reserved (0x05 - 0xFFFF) +}; + +/// HID Usage Table: Monitor Enumerated Page (0x81) +/// Intentionally skipped + +/// HID Usage Table: VESA Virtual Controls Page (0x82) +enum { + HID_USAGE_VESA_VIRTUAL_CONTROLS_DEGAUSS = 0x01, // DV + // Reserved (0x02 - 0x0F) + HID_USAGE_VESA_VIRTUAL_CONTROLS_BRIGHTNESS = 0x10, // DV + // Reserved (0x11) + HID_USAGE_VESA_VIRTUAL_CONTROLS_CONTRAST = 0x12, // DV + // Reserved (0x13 - 0x15) + HID_USAGE_VESA_VIRTUAL_CONTROLS_RED_VIDEO_GAIN = 0x16, // DV + // Reserved (0x17) + HID_USAGE_VESA_VIRTUAL_CONTROLS_GREEN_VIDEO_GAIN = 0x18, // DV + // Reserved (0x19) + HID_USAGE_VESA_VIRTUAL_CONTROLS_BLUE_VIDEO_GAIN = 0x1A, // DV + // Reserved (0x1B) + HID_USAGE_VESA_VIRTUAL_CONTROLS_FOCUS = 0x1C, // DV + // Reserved (0x1D - 0x1F) + HID_USAGE_VESA_VIRTUAL_CONTROLS_HORIZONTAL_POSITION = 0x20, // DV + // Reserved (0x21) + HID_USAGE_VESA_VIRTUAL_CONTROLS_HORIZONTAL_SIZE = 0x22, // DV + // Reserved (0x23) + HID_USAGE_VESA_VIRTUAL_CONTROLS_HORIZONTAL_PINCUSHION = 0x24, // DV + // Reserved (0x25) + HID_USAGE_VESA_VIRTUAL_CONTROLS_HORIZONTAL_PINCUSHION_BALANCE = 0x26, // DV + // Reserved (0x27) + HID_USAGE_VESA_VIRTUAL_CONTROLS_HORIZONTAL_MISCONVERGENCE = 0x28, // DV + // Reserved (0x29) + HID_USAGE_VESA_VIRTUAL_CONTROLS_HORIZONTAL_LINEARITY = 0x2A, // DV + // Reserved (0x2B) + HID_USAGE_VESA_VIRTUAL_CONTROLS_HORIZONTAL_LINEARITY_BALANCE = 0x2C, // DV + // Reserved (0x2D - 0x2F) + HID_USAGE_VESA_VIRTUAL_CONTROLS_VERTICAL_POSITION = 0x30, // DV + // Reserved (0x31) + HID_USAGE_VESA_VIRTUAL_CONTROLS_VERTICAL_SIZE = 0x32, // DV + // Reserved (0x33) + HID_USAGE_VESA_VIRTUAL_CONTROLS_VERTICAL_PINCUSHION = 0x34, // DV + // Reserved (0x35) + HID_USAGE_VESA_VIRTUAL_CONTROLS_VERTICAL_PINCUSHION_BALANCE = 0x36, // DV + // Reserved (0x37) + HID_USAGE_VESA_VIRTUAL_CONTROLS_VERTICAL_MISCONVERGENCE = 0x38, // DV + // Reserved (0x39) + HID_USAGE_VESA_VIRTUAL_CONTROLS_VERTICAL_LINEARITY = 0x3A, // DV + // Reserved (0x3B) + HID_USAGE_VESA_VIRTUAL_CONTROLS_VERTICAL_LINEARITY_BALANCE = 0x3C, // DV + // Reserved (0x3D - 0x3F) + HID_USAGE_VESA_VIRTUAL_CONTROLS_PARALLELOGRAM_DISTORTION = 0x40, // DV + // Reserved (0x41) + HID_USAGE_VESA_VIRTUAL_CONTROLS_TRAPEZOIDAL_DISTORTION = 0x42, // DV + // Reserved (0x43) + HID_USAGE_VESA_VIRTUAL_CONTROLS_TILT = 0x44, // DV + // Reserved (0x45) + HID_USAGE_VESA_VIRTUAL_CONTROLS_TOP_CORNER_DISTORTION_CONTROL = 0x46, // DV + // Reserved (0x47) + HID_USAGE_VESA_VIRTUAL_CONTROLS_TOP_CORNER_DISTORTION_BALANCE = 0x48, // DV + // Reserved (0x49) + HID_USAGE_VESA_VIRTUAL_CONTROLS_BOTTOM_CORNER_DISTORTION_CONTROL = 0x4A, // DV + // Reserved (0x4B) + HID_USAGE_VESA_VIRTUAL_CONTROLS_BOTTOM_CORNER_DISTORTION_BALANCE = 0x4C, // DV + // Reserved (0x4D - 0x55) + HID_USAGE_VESA_VIRTUAL_CONTROLS_HORIZONTAL_MOIRE = 0x56, // DV + // Reserved (0x57) + HID_USAGE_VESA_VIRTUAL_CONTROLS_VERTICAL_MOIRE = 0x58, // DV + // Reserved (0x59 - 0x5D) + HID_USAGE_VESA_VIRTUAL_CONTROLS_INPUT_LEVEL_SELECT = 0x5E, // NAry + // Reserved (0x5F) + HID_USAGE_VESA_VIRTUAL_CONTROLS_INPUT_SOURCE_SELECT = 0x60, // NAry + // Reserved (0x61 - 0x6B) + HID_USAGE_VESA_VIRTUAL_CONTROLS_RED_VIDEO_BLACK_LEVEL = 0x6C, // DV + // Reserved (0x6D) + HID_USAGE_VESA_VIRTUAL_CONTROLS_GREEN_VIDEO_BLACK_LEVEL = 0x6E, // DV + // Reserved (0x6F) + HID_USAGE_VESA_VIRTUAL_CONTROLS_BLUE_VIDEO_BLACK_LEVEL = 0x70, // DV + // Reserved (0x71 - 0xA1) + HID_USAGE_VESA_VIRTUAL_CONTROLS_AUTO_SIZE_CENTER = 0xA2, // NAry + // Reserved (0xA3) + HID_USAGE_VESA_VIRTUAL_CONTROLS_POLARITY_HORIZONTAL_SYNC = 0xA4, // NAry + // Reserved (0xA5) + HID_USAGE_VESA_VIRTUAL_CONTROLS_POLARITY_VERTICAL_SYNC = 0xA6, // NAry + // Reserved (0xA7) + HID_USAGE_VESA_VIRTUAL_CONTROLS_SYNC_TYPE = 0xA8, // NAry + // Reserved (0xA9) + HID_USAGE_VESA_VIRTUAL_CONTROLS_SCREEN_ORIENTATION = 0xAA, // NAry + // Reserved (0xAB) + HID_USAGE_VESA_VIRTUAL_CONTROLS_HORIZONTAL_FREQUENCY = 0xAC, // DV + // Reserved (0xAD) + HID_USAGE_VESA_VIRTUAL_CONTROLS_VERTICAL_FREQUENCY = 0xAE, // DV + // Reserved (0xAF) + HID_USAGE_VESA_VIRTUAL_CONTROLS_SETTINGS = 0xB0, // NAry + // Reserved (0xB1 - 0xC9) + HID_USAGE_VESA_VIRTUAL_CONTROLS_ON_SCREEN_DISPLAY = 0xCA, // NAry + // Reserved (0xCB - 0xD3) + HID_USAGE_VESA_VIRTUAL_CONTROLS_STEREO_MODE = 0xD4, // NAry + // Reserved (0xD5 - 0xFFFF) }; /// HID Usage Table: Power Device Page (0x84) @@ -1665,6 +2575,7 @@ enum { HID_USAGE_POWER_I_MANUFACTURER = 0xFD, HID_USAGE_POWER_I_PRODUCT = 0xFE, HID_USAGE_POWER_I_SERIAL_NUMBER = 0xFF + // Reserved (0x100 - 0xFFFF) }; /// HID Usage Table: Battery System Page (0x85) @@ -1772,6 +2683,48 @@ enum { // F4-FF Reserved }; +/// HID Usage Table: Camera Control Page (0x90) +enum { + // Reserved (0x01 - 0x1F) + HID_USAGE_CAMERA_CONTROL_CAMERA_AUTO_FOCUS = 0x20, // OSC + HID_USAGE_CAMERA_CONTROL_CAMERA_SHUTTER = 0x21 // OSC + // Reserved (0x22 - 0xFFFF) +}; + +/// HID Usage Table: Arcade Page (0x91) +enum { + HID_USAGE_ARCADE_GENERAL_PURPOSE_IO_CARD = 0x01, // CA + HID_USAGE_ARCADE_COIN_DOOR = 0x02, // CA + HID_USAGE_ARCADE_WATCHDOG_TIMER = 0x03, // CA + // Reserved (0x04 - 0x2F) + HID_USAGE_ARCADE_GENERAL_PURPOSE_ANALOG_INPUT_STATE = 0x30, // DV + HID_USAGE_ARCADE_GENERAL_PURPOSE_DIGITAL_INPUT_STATE = 0x31, // DV + HID_USAGE_ARCADE_GENERAL_PURPOSE_OPTICAL_INPUT_STATE = 0x32, // DV + HID_USAGE_ARCADE_GENERAL_PURPOSE_DIGITAL_OUTPUT_STATE = 0x33, // DV + HID_USAGE_ARCADE_NUMBER_OF_COIN_DOORS = 0x34, // DV + HID_USAGE_ARCADE_COIN_DRAWER_DROP_COUNT = 0x35, // DV + HID_USAGE_ARCADE_COIN_DRAWER_START = 0x36, // OOC + HID_USAGE_ARCADE_COIN_DRAWER_SERVICE = 0x37, // OOC + HID_USAGE_ARCADE_COIN_DRAWER_TILT = 0x38, // OOC + HID_USAGE_ARCADE_COIN_DOOR_TEST = 0x39, // OOC + // Reserved (0x3A - 0x3F) + HID_USAGE_ARCADE_COIN_DOOR_LOCKOUT = 0x40, // OOC + HID_USAGE_ARCADE_WATCHDOG_TIMEOUT = 0x41, // DV + HID_USAGE_ARCADE_WATCHDOG_ACTION = 0x42, // NAry + HID_USAGE_ARCADE_WATCHDOG_REBOOT = 0x43, // Sel + HID_USAGE_ARCADE_WATCHDOG_RESTART = 0x44, // Sel + HID_USAGE_ARCADE_ALARM_INPUT = 0x45, // DV + HID_USAGE_ARCADE_COIN_DOOR_COUNTER = 0x46, // OOC + HID_USAGE_ARCADE_IO_DIRECTION_MAPPING = 0x47, // DV + HID_USAGE_ARCADE_SET_IO_DIRECTION_MAPPING = 0x48, // DV + HID_USAGE_ARCADE_EXTENDED_OPTICAL_INPUT_STATE = 0x49, // DV + HID_USAGE_ARCADE_PIN_PAD_INPUT_STATE = 0x4A, // DV + HID_USAGE_ARCADE_PIN_PAD_STATUS = 0x4B, // DV + HID_USAGE_ARCADE_PIN_PAD_OUTPUT = 0x4C, // OOC + HID_USAGE_ARCADE_PIN_PAD_COMMAND = 0x4D, // DV + // Reserved (0x4E - 0xFFFF) +}; + /// HID Usage Table: FIDO Alliance Page (0xF1D0) enum { HID_USAGE_FIDO_U2FHID = 0x01, // U2FHID usage for top-level collection -- cgit v1.3.1 From 7e59f1bf8a7590e13da7fe5e8c602b41c95d2341 Mon Sep 17 00:00:00 2001 From: TenGui Date: Thu, 23 Jul 2026 15:26:18 -0700 Subject: fix narrowing, add cast --- src/device/usbd.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/device/usbd.h b/src/device/usbd.h index 9be12ed0a..296ec417d 100644 --- a/src/device/usbd.h +++ b/src/device/usbd.h @@ -229,7 +229,7 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ // Config number, interface count, string index, total length, attribute, power in mA #define TUD_CONFIG_DESCRIPTOR(config_num, _itfcount, _stridx, _total_len, _attribute, _power_ma) \ - 9, TUSB_DESC_CONFIGURATION, U16_TO_U8S_LE(_total_len), _itfcount, config_num, _stridx, TU_BIT(7) | _attribute, (_power_ma)/2 + 9, TUSB_DESC_CONFIGURATION, U16_TO_U8S_LE(_total_len), _itfcount, config_num, _stridx, TU_BIT(7) | _attribute, (uint8_t)((_power_ma)/2) //--------------------------------------------------------------------+ // CDC Descriptor Templates -- cgit v1.3.1 From ca198f3dac0b61f3a13bc3319223ed37511e568c Mon Sep 17 00:00:00 2001 From: TenGui Date: Thu, 23 Jul 2026 15:58:28 -0700 Subject: also fix tusb_types --- src/common/tusb_types.h | 416 ++++++++++++++++++++++++++---------------------- 1 file changed, 223 insertions(+), 193 deletions(-) (limited to 'src') diff --git a/src/common/tusb_types.h b/src/common/tusb_types.h index d49d277ed..5a91d6182 100644 --- a/src/common/tusb_types.h +++ b/src/common/tusb_types.h @@ -13,43 +13,49 @@ #include "tusb_compiler.h" #ifdef __cplusplus - extern "C" { +extern "C" { #endif //------------- Device DCache declaration -------------// -#define TUD_EPBUF_DCACHE_SIZE(_size) (CFG_TUD_MEM_DCACHE_ENABLE ? \ - (TU_DIV_CEIL(_size, CFG_TUD_MEM_DCACHE_LINE_SIZE) * CFG_TUD_MEM_DCACHE_LINE_SIZE) : (_size)) +#define TUD_EPBUF_DCACHE_SIZE(_size) \ + (CFG_TUD_MEM_DCACHE_ENABLE ? (TU_DIV_CEIL(_size, CFG_TUD_MEM_DCACHE_LINE_SIZE) * CFG_TUD_MEM_DCACHE_LINE_SIZE) \ + : (_size)) // Declare an endpoint buffer with uint8_t[size] -#define TUD_EPBUF_DEF(_name, _size) \ - union { \ - CFG_TUD_MEM_ALIGN uint8_t _name[_size]; \ - TU_ATTR_ALIGNED(CFG_TUD_MEM_DCACHE_ENABLE ? CFG_TUD_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUD_EPBUF_DCACHE_SIZE(_size)]; \ +#define TUD_EPBUF_DEF(_name, _size) \ + union { \ + CFG_TUD_MEM_ALIGN uint8_t _name[_size]; \ + TU_ATTR_ALIGNED(CFG_TUD_MEM_DCACHE_ENABLE ? CFG_TUD_MEM_DCACHE_LINE_SIZE : 1) \ + uint8_t _name##_dcache_padding[TUD_EPBUF_DCACHE_SIZE(_size)]; \ } // Declare an endpoint buffer with a type -#define TUD_EPBUF_TYPE_DEF(_type, _name) \ - union { \ - CFG_TUD_MEM_ALIGN _type _name; \ - TU_ATTR_ALIGNED(CFG_TUD_MEM_DCACHE_ENABLE ? CFG_TUD_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUD_EPBUF_DCACHE_SIZE(sizeof(_type))]; \ +#define TUD_EPBUF_TYPE_DEF(_type, _name) \ + union { \ + CFG_TUD_MEM_ALIGN _type _name; \ + TU_ATTR_ALIGNED(CFG_TUD_MEM_DCACHE_ENABLE ? CFG_TUD_MEM_DCACHE_LINE_SIZE : 1) \ + uint8_t _name##_dcache_padding[TUD_EPBUF_DCACHE_SIZE(sizeof(_type))]; \ } //------------- Host DCache declaration -------------// -#define TUH_EPBUF_DCACHE_SIZE(_size) (CFG_TUH_MEM_DCACHE_ENABLE ? \ - (TU_DIV_CEIL(_size, CFG_TUH_MEM_DCACHE_LINE_SIZE) * CFG_TUH_MEM_DCACHE_LINE_SIZE) : (_size)) +#define TUH_EPBUF_DCACHE_SIZE(_size) \ + (CFG_TUH_MEM_DCACHE_ENABLE ? (TU_DIV_CEIL(_size, CFG_TUH_MEM_DCACHE_LINE_SIZE) * CFG_TUH_MEM_DCACHE_LINE_SIZE) \ + : (_size)) // Declare an endpoint buffer with uint8_t[size] -#define TUH_EPBUF_DEF(_name, _size) \ - union { \ - CFG_TUH_MEM_ALIGN uint8_t _name[_size]; \ - TU_ATTR_ALIGNED(CFG_TUH_MEM_DCACHE_ENABLE ? CFG_TUH_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUH_EPBUF_DCACHE_SIZE(_size)]; \ +#define TUH_EPBUF_DEF(_name, _size) \ + union { \ + CFG_TUH_MEM_ALIGN uint8_t _name[_size]; \ + TU_ATTR_ALIGNED(CFG_TUH_MEM_DCACHE_ENABLE ? CFG_TUH_MEM_DCACHE_LINE_SIZE : 1) \ + uint8_t _name##_dcache_padding[TUH_EPBUF_DCACHE_SIZE(_size)]; \ } // Declare an endpoint buffer with a type -#define TUH_EPBUF_TYPE_DEF(_type, _name) \ - union { \ - CFG_TUH_MEM_ALIGN _type _name; \ - TU_ATTR_ALIGNED(CFG_TUH_MEM_DCACHE_ENABLE ? CFG_TUH_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUH_EPBUF_DCACHE_SIZE(sizeof(_type))]; \ +#define TUH_EPBUF_TYPE_DEF(_type, _name) \ + union { \ + CFG_TUH_MEM_ALIGN _type _name; \ + TU_ATTR_ALIGNED(CFG_TUH_MEM_DCACHE_ENABLE ? CFG_TUH_MEM_DCACHE_LINE_SIZE : 1) \ + uint8_t _name##_dcache_padding[TUH_EPBUF_DCACHE_SIZE(sizeof(_type))]; \ } @@ -65,10 +71,10 @@ typedef enum { /// defined base on EHCI specs value for Endpoint Speed typedef enum { - TUSB_SPEED_FULL = 0, - TUSB_SPEED_LOW = 1, - TUSB_SPEED_HIGH = 2, - TUSB_SPEED_AUTO = 0xaa, + TUSB_SPEED_FULL = 0, + TUSB_SPEED_LOW = 1, + TUSB_SPEED_HIGH = 2, + TUSB_SPEED_AUTO = 0xaa, TUSB_SPEED_INVALID = 0xff, } tusb_speed_t; @@ -99,18 +105,18 @@ enum { }; // Endpoint Bulk size depending on host/device max speed -#define TUD_EPSIZE_BULK_MAX (TUD_OPT_HIGH_SPEED ? 512 : 64) -#define TUH_EPSIZE_BULK_MAX (TUH_OPT_HIGH_SPEED ? 512 : 64) +#define TUD_EPSIZE_BULK_MAX (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define TUH_EPSIZE_BULK_MAX (TUH_OPT_HIGH_SPEED ? 512 : 64) /// Isochronous Endpoint Attributes typedef enum { - TUSB_ISO_EP_ATT_NO_SYNC = 0x00, - TUSB_ISO_EP_ATT_ASYNCHRONOUS = 0x04, - TUSB_ISO_EP_ATT_ADAPTIVE = 0x08, - TUSB_ISO_EP_ATT_SYNCHRONOUS = 0x0C, - TUSB_ISO_EP_ATT_DATA = 0x00, ///< Data End Point - TUSB_ISO_EP_ATT_EXPLICIT_FB = 0x10, ///< Feedback End Point - TUSB_ISO_EP_ATT_IMPLICIT_FB = 0x20, ///< Data endpoint that also serves as an implicit feedback + TUSB_ISO_EP_ATT_NO_SYNC = 0x00, + TUSB_ISO_EP_ATT_ASYNCHRONOUS = 0x04, + TUSB_ISO_EP_ATT_ADAPTIVE = 0x08, + TUSB_ISO_EP_ATT_SYNCHRONOUS = 0x0C, + TUSB_ISO_EP_ATT_DATA = 0x00, ///< Data End Point + TUSB_ISO_EP_ATT_EXPLICIT_FB = 0x10, ///< Feedback End Point + TUSB_ISO_EP_ATT_IMPLICIT_FB = 0x20, ///< Data endpoint that also serves as an implicit feedback } tusb_iso_ep_attribute_t; /// USB Descriptor Types @@ -127,35 +133,35 @@ typedef enum { TUSB_DESC_DEBUG = 0x0A, TUSB_DESC_INTERFACE_ASSOCIATION = 0x0B, - TUSB_DESC_BOS = 0x0F, - TUSB_DESC_DEVICE_CAPABILITY = 0x10, + TUSB_DESC_BOS = 0x0F, + TUSB_DESC_DEVICE_CAPABILITY = 0x10, - TUSB_DESC_FUNCTIONAL = 0x21, + TUSB_DESC_FUNCTIONAL = 0x21, // Class Specific Descriptor - TUSB_DESC_CS_DEVICE = 0x21, - TUSB_DESC_CS_CONFIGURATION = 0x22, - TUSB_DESC_CS_STRING = 0x23, - TUSB_DESC_CS_INTERFACE = 0x24, - TUSB_DESC_CS_ENDPOINT = 0x25, + TUSB_DESC_CS_DEVICE = 0x21, + TUSB_DESC_CS_CONFIGURATION = 0x22, + TUSB_DESC_CS_STRING = 0x23, + TUSB_DESC_CS_INTERFACE = 0x24, + TUSB_DESC_CS_ENDPOINT = 0x25, TUSB_DESC_SUPERSPEED_ENDPOINT_COMPANION = 0x30, TUSB_DESC_SUPERSPEED_ISO_ENDPOINT_COMPANION = 0x31 } tusb_desc_type_t; typedef enum { - TUSB_REQ_GET_STATUS = 0 , - TUSB_REQ_CLEAR_FEATURE = 1 , - TUSB_REQ_RESERVED = 2 , - TUSB_REQ_SET_FEATURE = 3 , - TUSB_REQ_RESERVED2 = 4 , - TUSB_REQ_SET_ADDRESS = 5 , - TUSB_REQ_GET_DESCRIPTOR = 6 , - TUSB_REQ_SET_DESCRIPTOR = 7 , - TUSB_REQ_GET_CONFIGURATION = 8 , - TUSB_REQ_SET_CONFIGURATION = 9 , - TUSB_REQ_GET_INTERFACE = 10 , - TUSB_REQ_SET_INTERFACE = 11 , + TUSB_REQ_GET_STATUS = 0, + TUSB_REQ_CLEAR_FEATURE = 1, + TUSB_REQ_RESERVED = 2, + TUSB_REQ_SET_FEATURE = 3, + TUSB_REQ_RESERVED2 = 4, + TUSB_REQ_SET_ADDRESS = 5, + TUSB_REQ_GET_DESCRIPTOR = 6, + TUSB_REQ_SET_DESCRIPTOR = 7, + TUSB_REQ_GET_CONFIGURATION = 8, + TUSB_REQ_SET_CONFIGURATION = 9, + TUSB_REQ_GET_INTERFACE = 10, + TUSB_REQ_SET_INTERFACE = 11, TUSB_REQ_SYNCH_FRAME = 12 } tusb_request_code_t; @@ -173,7 +179,7 @@ typedef enum { } tusb_request_type_t; typedef enum { - TUSB_REQ_RCPT_DEVICE =0, + TUSB_REQ_RCPT_DEVICE = 0, TUSB_REQ_RCPT_INTERFACE, TUSB_REQ_RCPT_ENDPOINT, TUSB_REQ_RCPT_OTHER @@ -181,42 +187,41 @@ typedef enum { // https://www.usb.org/defined-class-codes typedef enum { - TUSB_CLASS_UNSPECIFIED = 0 , - TUSB_CLASS_AUDIO = 1 , - TUSB_CLASS_CDC = 2 , - TUSB_CLASS_HID = 3 , - TUSB_CLASS_RESERVED_4 = 4 , - TUSB_CLASS_PHYSICAL = 5 , - TUSB_CLASS_IMAGE = 6 , - TUSB_CLASS_PRINTER = 7 , - TUSB_CLASS_MSC = 8 , - TUSB_CLASS_HUB = 9 , - TUSB_CLASS_CDC_DATA = 10 , - TUSB_CLASS_SMART_CARD = 11 , - TUSB_CLASS_RESERVED_12 = 12 , - TUSB_CLASS_CONTENT_SECURITY = 13 , - TUSB_CLASS_VIDEO = 14 , - TUSB_CLASS_PERSONAL_HEALTHCARE = 15 , - TUSB_CLASS_AUDIO_VIDEO = 16 , - - TUSB_CLASS_DIAGNOSTIC = 0xDC , - TUSB_CLASS_WIRELESS_CONTROLLER = 0xE0 , - TUSB_CLASS_MISC = 0xEF , - TUSB_CLASS_APPLICATION_SPECIFIC = 0xFE , + TUSB_CLASS_UNSPECIFIED = 0, + TUSB_CLASS_AUDIO = 1, + TUSB_CLASS_CDC = 2, + TUSB_CLASS_HID = 3, + TUSB_CLASS_RESERVED_4 = 4, + TUSB_CLASS_PHYSICAL = 5, + TUSB_CLASS_IMAGE = 6, + TUSB_CLASS_PRINTER = 7, + TUSB_CLASS_MSC = 8, + TUSB_CLASS_HUB = 9, + TUSB_CLASS_CDC_DATA = 10, + TUSB_CLASS_SMART_CARD = 11, + TUSB_CLASS_RESERVED_12 = 12, + TUSB_CLASS_CONTENT_SECURITY = 13, + TUSB_CLASS_VIDEO = 14, + TUSB_CLASS_PERSONAL_HEALTHCARE = 15, + TUSB_CLASS_AUDIO_VIDEO = 16, + + TUSB_CLASS_DIAGNOSTIC = 0xDC, + TUSB_CLASS_WIRELESS_CONTROLLER = 0xE0, + TUSB_CLASS_MISC = 0xEF, + TUSB_CLASS_APPLICATION_SPECIFIC = 0xFE, TUSB_CLASS_VENDOR_SPECIFIC = 0xFF } tusb_class_code_t; -typedef enum -{ +typedef enum { MISC_SUBCLASS_COMMON = 2 -}misc_subclass_type_t; +} misc_subclass_type_t; typedef enum { MISC_PROTOCOL_IAD = 1 } misc_protocol_type_t; typedef enum { - APP_SUBCLASS_USBTMC = 0x03, + APP_SUBCLASS_USBTMC = 0x03, APP_SUBCLASS_DFU_RUNTIME = 0x01 } app_subclass_type_t; @@ -244,14 +249,14 @@ enum { TUSB_DESC_CONFIG_ATT_SELF_POWERED = 1 << 6, }; -#define TUSB_DESC_CONFIG_POWER_MA(x) ((x)/2) +#define TUSB_DESC_CONFIG_POWER_MA(x) (uint8_t)((x) / 2) // USB 2.0 Spec Table 9-7: Test Mode Selectors typedef enum { - TUSB_FEATURE_TEST_J = 1, - TUSB_FEATURE_TEST_K = 2, - TUSB_FEATURE_TEST_SE0_NAK = 3, - TUSB_FEATURE_TEST_PACKET = 4, + TUSB_FEATURE_TEST_J = 1, + TUSB_FEATURE_TEST_K = 2, + TUSB_FEATURE_TEST_SE0_NAK = 3, + TUSB_FEATURE_TEST_PACKET = 4, TUSB_FEATURE_TEST_FORCE_ENABLE = 5, } tusb_feature_test_mode_t; @@ -271,8 +276,8 @@ typedef enum { // TODO remove enum { - DESC_OFFSET_LEN = 0, - DESC_OFFSET_TYPE = 1, + DESC_OFFSET_LEN = 0, + DESC_OFFSET_TYPE = 1, DESC_OFFSET_SUBTYPE = 2 }; @@ -304,8 +309,8 @@ enum { }; enum { - TU_EP0_OUT = 0x00, - TU_EP0_IN = 0x80 + TU_EP0_OUT = 0x00, + TU_EP0_IN = 0x80 }; @@ -313,7 +318,7 @@ enum { // //--------------------------------------------------------------------+ typedef struct { - tusb_role_t role; + tusb_role_t role; tusb_speed_t speed; } tusb_rhport_init_t; @@ -332,77 +337,101 @@ TU_ATTR_BIT_FIELD_ORDER_BEGIN /// USB Device Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength ; ///< Size of this descriptor in bytes. - uint8_t bDescriptorType ; ///< DEVICE Descriptor Type. - uint16_t bcdUSB ; ///< BUSB Specification Release Number in Binary-Coded Decimal (i.e., 2.10 is 210H). - uint8_t bDeviceClass ; ///< Class code (assigned by the USB-IF). - uint8_t bDeviceSubClass ; ///< Subclass code (assigned by the USB-IF). - uint8_t bDeviceProtocol ; ///< Protocol code (assigned by the USB-IF). - uint8_t bMaxPacketSize0 ; ///< Maximum packet size for endpoint zero (only 8, 16, 32, or 64 are valid). For HS devices is fixed to 64. - uint16_t idVendor ; ///< Vendor ID (assigned by the USB-IF). - uint16_t idProduct ; ///< Product ID (assigned by the manufacturer). - uint16_t bcdDevice ; ///< Device release number in binary-coded decimal. - uint8_t iManufacturer ; ///< Index of string descriptor describing manufacturer. - uint8_t iProduct ; ///< Index of string descriptor describing product. - uint8_t iSerialNumber ; ///< Index of string descriptor describing the device's serial number. - uint8_t bNumConfigurations ; ///< Number of possible configurations. + uint8_t bLength; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType; ///< DEVICE Descriptor Type. + uint16_t bcdUSB; ///< BUSB Specification Release Number in Binary-Coded Decimal (i.e., 2.10 is 210H). + uint8_t bDeviceClass; ///< Class code (assigned by the USB-IF). + uint8_t bDeviceSubClass; ///< Subclass code (assigned by the USB-IF). + uint8_t bDeviceProtocol; ///< Protocol code (assigned by the USB-IF). + uint8_t bMaxPacketSize0; ///< Maximum packet size for endpoint zero (only 8, 16, 32, or 64 are valid). For HS devices + ///< is fixed to 64. + uint16_t idVendor; ///< Vendor ID (assigned by the USB-IF). + uint16_t idProduct; ///< Product ID (assigned by the manufacturer). + uint16_t bcdDevice; ///< Device release number in binary-coded decimal. + uint8_t iManufacturer; ///< Index of string descriptor describing manufacturer. + uint8_t iProduct; ///< Index of string descriptor describing product. + uint8_t iSerialNumber; ///< Index of string descriptor describing the device's serial number. + uint8_t bNumConfigurations; ///< Number of possible configurations. } tusb_desc_device_t; -TU_VERIFY_STATIC( sizeof(tusb_desc_device_t) == 18u, "size is not correct"); +TU_VERIFY_STATIC(sizeof(tusb_desc_device_t) == 18u, "size is not correct"); // USB Binary Device Object Store (BOS) Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength ; ///< Size of this descriptor in bytes - uint8_t bDescriptorType ; ///< CONFIGURATION Descriptor Type - uint16_t wTotalLength ; ///< Total length of data returned for this descriptor - uint8_t bNumDeviceCaps ; ///< Number of device capability descriptors in the BOS + uint8_t bLength; ///< Size of this descriptor in bytes + uint8_t bDescriptorType; ///< CONFIGURATION Descriptor Type + uint16_t wTotalLength; ///< Total length of data returned for this descriptor + uint8_t bNumDeviceCaps; ///< Number of device capability descriptors in the BOS } tusb_desc_bos_t; -TU_VERIFY_STATIC( sizeof(tusb_desc_bos_t) == 5u, "size is not correct"); +TU_VERIFY_STATIC(sizeof(tusb_desc_bos_t) == 5u, "size is not correct"); /// USB Configuration Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength ; ///< Size of this descriptor in bytes - uint8_t bDescriptorType ; ///< CONFIGURATION Descriptor Type - uint16_t wTotalLength ; ///< Total length of data returned for this configuration. Includes the combined length of all descriptors (configuration, interface, endpoint, and class- or vendor-specific) returned for this configuration. - - uint8_t bNumInterfaces ; ///< Number of interfaces supported by this configuration - uint8_t bConfigurationValue ; ///< Value to use as an argument to the SetConfiguration() request to select this configuration. - uint8_t iConfiguration ; ///< Index of string descriptor describing this configuration - uint8_t bmAttributes ; ///< Configuration characteristics \n D7: Reserved (set to one)\n D6: Self-powered \n D5: Remote Wakeup \n D4...0: Reserved (reset to zero) \n D7 is reserved and must be set to one for historical reasons. \n A device configuration that uses power from the bus and a local source reports a non-zero value in bMaxPower to indicate the amount of bus power required and sets D6. The actual power source at runtime may be determined using the GetStatus(DEVICE) request (see USB 2.0 spec Section 9.4.5). \n If a device configuration supports remote wakeup, D5 is set to one. - uint8_t bMaxPower ; ///< Maximum power consumption of the USB device from the bus in this specific configuration when the device is fully operational. Expressed in 2 mA units (i.e., 50 = 100 mA). + uint8_t bLength; ///< Size of this descriptor in bytes + uint8_t bDescriptorType; ///< CONFIGURATION Descriptor Type + uint16_t wTotalLength; ///< Total length of data returned for this configuration. Includes the combined length of all + ///< descriptors (configuration, interface, endpoint, and class- or vendor-specific) returned + ///< for this configuration. + + uint8_t bNumInterfaces; ///< Number of interfaces supported by this configuration + uint8_t bConfigurationValue; ///< Value to use as an argument to the SetConfiguration() request to select this + ///< configuration. + uint8_t iConfiguration; ///< Index of string descriptor describing this configuration + uint8_t bmAttributes; ///< Configuration characteristics \n D7: Reserved (set to one)\n D6: Self-powered \n D5: Remote + ///< Wakeup \n D4...0: Reserved (reset to zero) \n D7 is reserved and must be set to one for + ///< historical reasons. \n A device configuration that uses power from the bus and a local + ///< source reports a non-zero value in bMaxPower to indicate the amount of bus power required + ///< and sets D6. The actual power source at runtime may be determined using the + ///< GetStatus(DEVICE) request (see USB 2.0 spec Section 9.4.5). \n If a device configuration + ///< supports remote wakeup, D5 is set to one. + uint8_t bMaxPower; ///< Maximum power consumption of the USB device from the bus in this specific configuration when + ///< the device is fully operational. Expressed in 2 mA units (i.e., 50 = 100 mA). } tusb_desc_configuration_t; -TU_VERIFY_STATIC( sizeof(tusb_desc_configuration_t) == 9u, "size is not correct"); +TU_VERIFY_STATIC(sizeof(tusb_desc_configuration_t) == 9u, "size is not correct"); /// USB Interface Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength ; ///< Size of this descriptor in bytes - uint8_t bDescriptorType ; ///< INTERFACE Descriptor Type - - uint8_t bInterfaceNumber ; ///< Number of this interface. Zero-based value identifying the index in the array of concurrent interfaces supported by this configuration. - uint8_t bAlternateSetting ; ///< Value used to select this alternate setting for the interface identified in the prior field - uint8_t bNumEndpoints ; ///< Number of endpoints used by this interface (excluding endpoint zero). If this value is zero, this interface only uses the Default Control Pipe. - uint8_t bInterfaceClass ; ///< Class code (assigned by the USB-IF). \li A value of zero is reserved for future standardization. \li If this field is set to FFH, the interface class is vendor-specific. \li All other values are reserved for assignment by the USB-IF. - uint8_t bInterfaceSubClass ; ///< Subclass code (assigned by the USB-IF). \n These codes are qualified by the value of the bInterfaceClass field. \li If the bInterfaceClass field is reset to zero, this field must also be reset to zero. \li If the bInterfaceClass field is not set to FFH, all values are reserved for assignment by the USB-IF. - uint8_t bInterfaceProtocol ; ///< Protocol code (assigned by the USB). \n These codes are qualified by the value of the bInterfaceClass and the bInterfaceSubClass fields. If an interface supports class-specific requests, this code identifies the protocols that the device uses as defined by the specification of the device class. \li If this field is reset to zero, the device does not use a class-specific protocol on this interface. \li If this field is set to FFH, the device uses a vendor-specific protocol for this interface. - uint8_t iInterface ; ///< Index of string descriptor describing this interface + uint8_t bLength; ///< Size of this descriptor in bytes + uint8_t bDescriptorType; ///< INTERFACE Descriptor Type + + uint8_t bInterfaceNumber; ///< Number of this interface. Zero-based value identifying the index in the array of + ///< concurrent interfaces supported by this configuration. + uint8_t + bAlternateSetting; ///< Value used to select this alternate setting for the interface identified in the prior field + uint8_t bNumEndpoints; ///< Number of endpoints used by this interface (excluding endpoint zero). If this value is + ///< zero, this interface only uses the Default Control Pipe. + uint8_t bInterfaceClass; ///< Class code (assigned by the USB-IF). \li A value of zero is reserved for future + ///< standardization. \li If this field is set to FFH, the interface class is + ///< vendor-specific. \li All other values are reserved for assignment by the USB-IF. + uint8_t bInterfaceSubClass; ///< Subclass code (assigned by the USB-IF). \n These codes are qualified by the value of + ///< the bInterfaceClass field. \li If the bInterfaceClass field is reset to zero, this + ///< field must also be reset to zero. \li If the bInterfaceClass field is not set to FFH, + ///< all values are reserved for assignment by the USB-IF. + uint8_t bInterfaceProtocol; ///< Protocol code (assigned by the USB). \n These codes are qualified by the value of the + ///< bInterfaceClass and the bInterfaceSubClass fields. If an interface supports + ///< class-specific requests, this code identifies the protocols that the device uses as + ///< defined by the specification of the device class. \li If this field is reset to zero, + ///< the device does not use a class-specific protocol on this interface. \li If this + ///< field is set to FFH, the device uses a vendor-specific protocol for this interface. + uint8_t iInterface; ///< Index of string descriptor describing this interface } tusb_desc_interface_t; -TU_VERIFY_STATIC( sizeof(tusb_desc_interface_t) == 9u, "size is not correct"); +TU_VERIFY_STATIC(sizeof(tusb_desc_interface_t) == 9u, "size is not correct"); /// USB Endpoint Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength ; // Size of this descriptor in bytes - uint8_t bDescriptorType ; // ENDPOINT Descriptor Type + uint8_t bLength; // Size of this descriptor in bytes + uint8_t bDescriptorType; // ENDPOINT Descriptor Type - uint8_t bEndpointAddress ; // The address of the endpoint + uint8_t bEndpointAddress; // The address of the endpoint struct TU_ATTR_PACKED { #if (TU_BITFIELD_ORDER == TU_BITFIELD_LE) - uint8_t xfer : 2; // Control, ISO, Bulk, Interrupt - uint8_t sync : 2; // None, Asynchronous, Adaptive, Synchronous - uint8_t usage : 2; // Data, Feedback, Implicit feedback + uint8_t xfer : 2; // Control, ISO, Bulk, Interrupt + uint8_t sync : 2; // None, Asynchronous, Adaptive, Synchronous + uint8_t usage : 2; // Data, Feedback, Implicit feedback uint8_t : 2; #elif (TU_BITFIELD_ORDER == TU_BITFIELD_BE) uint8_t : 2; @@ -414,70 +443,70 @@ typedef struct TU_ATTR_PACKED { #endif } bmAttributes; - uint16_t wMaxPacketSize ; // Bit 10..0 : max packet size, bit 12..11 additional transaction per highspeed micro-frame - uint8_t bInterval ; // Polling interval, in frames or microframes depending on the operating speed + uint16_t wMaxPacketSize; // Bit 10..0 : max packet size, bit 12..11 additional transaction per highspeed micro-frame + uint8_t bInterval; // Polling interval, in frames or microframes depending on the operating speed } tusb_desc_endpoint_t; -TU_VERIFY_STATIC( sizeof(tusb_desc_endpoint_t) == 7u, "size is not correct"); +TU_VERIFY_STATIC(sizeof(tusb_desc_endpoint_t) == 7u, "size is not correct"); /// USB Other Speed Configuration Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength ; ///< Size of descriptor - uint8_t bDescriptorType ; ///< Other_speed_Configuration Type - uint16_t wTotalLength ; ///< Total length of data returned - - uint8_t bNumInterfaces ; ///< Number of interfaces supported by this speed configuration - uint8_t bConfigurationValue ; ///< Value to use to select configuration - uint8_t iConfiguration ; ///< Index of string descriptor - uint8_t bmAttributes ; ///< Same as Configuration descriptor - uint8_t bMaxPower ; ///< Same as Configuration descriptor + uint8_t bLength; ///< Size of descriptor + uint8_t bDescriptorType; ///< Other_speed_Configuration Type + uint16_t wTotalLength; ///< Total length of data returned + + uint8_t bNumInterfaces; ///< Number of interfaces supported by this speed configuration + uint8_t bConfigurationValue; ///< Value to use to select configuration + uint8_t iConfiguration; ///< Index of string descriptor + uint8_t bmAttributes; ///< Same as Configuration descriptor + uint8_t bMaxPower; ///< Same as Configuration descriptor } tusb_desc_other_speed_t; /// USB Device Qualifier Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength ; ///< Size of descriptor - uint8_t bDescriptorType ; ///< Device Qualifier Type - uint16_t bcdUSB ; ///< USB specification version number (e.g., 0200H for V2.00) + uint8_t bLength; ///< Size of descriptor + uint8_t bDescriptorType; ///< Device Qualifier Type + uint16_t bcdUSB; ///< USB specification version number (e.g., 0200H for V2.00) - uint8_t bDeviceClass ; ///< Class Code - uint8_t bDeviceSubClass ; ///< SubClass Code - uint8_t bDeviceProtocol ; ///< Protocol Code + uint8_t bDeviceClass; ///< Class Code + uint8_t bDeviceSubClass; ///< SubClass Code + uint8_t bDeviceProtocol; ///< Protocol Code - uint8_t bMaxPacketSize0 ; ///< Maximum packet size for other speed - uint8_t bNumConfigurations ; ///< Number of Other-speed Configurations - uint8_t bReserved ; ///< Reserved for future use, must be zero + uint8_t bMaxPacketSize0; ///< Maximum packet size for other speed + uint8_t bNumConfigurations; ///< Number of Other-speed Configurations + uint8_t bReserved; ///< Reserved for future use, must be zero } tusb_desc_device_qualifier_t; -TU_VERIFY_STATIC( sizeof(tusb_desc_device_qualifier_t) == 10u, "size is not correct"); +TU_VERIFY_STATIC(sizeof(tusb_desc_device_qualifier_t) == 10u, "size is not correct"); /// USB Interface Association Descriptor (IAD ECN) typedef struct TU_ATTR_PACKED { - uint8_t bLength ; ///< Size of descriptor - uint8_t bDescriptorType ; ///< Other_speed_Configuration Type + uint8_t bLength; ///< Size of descriptor + uint8_t bDescriptorType; ///< Other_speed_Configuration Type - uint8_t bFirstInterface ; ///< Index of the first associated interface. - uint8_t bInterfaceCount ; ///< Total number of associated interfaces. + uint8_t bFirstInterface; ///< Index of the first associated interface. + uint8_t bInterfaceCount; ///< Total number of associated interfaces. - uint8_t bFunctionClass ; ///< Interface class ID. - uint8_t bFunctionSubClass ; ///< Interface subclass ID. - uint8_t bFunctionProtocol ; ///< Interface protocol ID. + uint8_t bFunctionClass; ///< Interface class ID. + uint8_t bFunctionSubClass; ///< Interface subclass ID. + uint8_t bFunctionProtocol; ///< Interface protocol ID. - uint8_t iFunction ; ///< Index of the string descriptor describing the interface association. + uint8_t iFunction; ///< Index of the string descriptor describing the interface association. } tusb_desc_interface_assoc_t; -TU_VERIFY_STATIC( sizeof(tusb_desc_interface_assoc_t) == 8u, "size is not correct"); +TU_VERIFY_STATIC(sizeof(tusb_desc_interface_assoc_t) == 8u, "size is not correct"); // USB String Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength ; ///< Size of this descriptor in bytes - uint8_t bDescriptorType ; ///< Descriptor Type + uint8_t bLength; ///< Size of this descriptor in bytes + uint8_t bDescriptorType; ///< Descriptor Type uint16_t utf16le[]; } tusb_desc_string_t; // USB Binary Device Object Store (BOS) typedef struct TU_ATTR_PACKED { uint8_t bLength; - uint8_t bDescriptorType ; + uint8_t bDescriptorType; uint8_t bDevCapabilityType; uint8_t bReserved; uint8_t PlatformCapabilityUUID[16]; @@ -494,8 +523,8 @@ typedef struct TU_ATTR_PACKED { // DFU Functional Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength; - uint8_t bDescriptorType; + uint8_t bLength; + uint8_t bDescriptorType; union { struct TU_ATTR_PACKED { @@ -522,13 +551,13 @@ typedef struct TU_ATTR_PACKED { union { struct TU_ATTR_PACKED { #if (TU_BITFIELD_ORDER == TU_BITFIELD_LE) - uint8_t recipient : 5; ///< Recipient type tusb_request_recipient_t. - uint8_t type : 2; ///< Request type tusb_request_type_t. - uint8_t direction : 1; ///< Direction type. tusb_dir_t + uint8_t recipient : 5; ///< Recipient type tusb_request_recipient_t. + uint8_t type : 2; ///< Request type tusb_request_type_t. + uint8_t direction : 1; ///< Direction type. tusb_dir_t #elif (TU_BITFIELD_ORDER == TU_BITFIELD_BE) - uint8_t direction : 1; ///< Direction type. tusb_dir_t - uint8_t type : 2; ///< Request type tusb_request_type_t. - uint8_t recipient : 5; ///< Recipient type tusb_request_recipient_t. + uint8_t direction : 1; ///< Direction type. tusb_dir_t + uint8_t type : 2; ///< Request type tusb_request_type_t. + uint8_t recipient : 5; ///< Recipient type tusb_request_recipient_t. #else #error "Please define TU_BITFIELD_ORDER as TU_BITFIELD_LE or TU_BITFIELD_BE" #endif @@ -543,42 +572,43 @@ typedef struct TU_ATTR_PACKED { uint16_t wLength; } tusb_control_request_t; -TU_VERIFY_STATIC( sizeof(tusb_control_request_t) == 8u, "size is not correct"); +TU_VERIFY_STATIC(sizeof(tusb_control_request_t) == 8u, "size is not correct"); -TU_ATTR_PACKED_END // End of all packed definitions -TU_ATTR_BIT_FIELD_ORDER_END +TU_ATTR_PACKED_END // End of all packed definitions + TU_ATTR_BIT_FIELD_ORDER_END -//--------------------------------------------------------------------+ -// Endpoint helper -//--------------------------------------------------------------------+ + //--------------------------------------------------------------------+ + // Endpoint helper + //--------------------------------------------------------------------+ -// Get direction from Endpoint address -TU_ATTR_ALWAYS_INLINE static inline tusb_dir_t tu_edpt_dir(uint8_t addr) { + // Get direction from Endpoint address + TU_ATTR_ALWAYS_INLINE static inline tusb_dir_t + tu_edpt_dir(uint8_t addr) { return (addr & TUSB_DIR_IN_MASK) ? TUSB_DIR_IN : TUSB_DIR_OUT; } // Get Endpoint number from address TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_edpt_number(uint8_t addr) { - return (uint8_t) (addr & TUSB_EPNUM_MASK); + return (uint8_t)(addr & TUSB_EPNUM_MASK); } TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_edpt_addr(uint8_t num, uint8_t dir) { - return (uint8_t) (num | (dir == (uint8_t)TUSB_DIR_IN ? (uint8_t)TUSB_DIR_IN_MASK : 0u)); + return (uint8_t)(num | (dir == (uint8_t)TUSB_DIR_IN ? (uint8_t)TUSB_DIR_IN_MASK : 0u)); } -TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_edpt_packet_size(tusb_desc_endpoint_t const* desc_ep) { +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_edpt_packet_size(const tusb_desc_endpoint_t *desc_ep) { return tu_le16toh(desc_ep->wMaxPacketSize) & 0x7FF; } #if CFG_TUSB_DEBUG TU_ATTR_ALWAYS_INLINE static inline const char *tu_edpt_type_str(tusb_xfer_type_t t) { - tu_static const char *str[] = {"control", "isochronous", "bulk", "interrupt"}; + const tu_static char *str[] = {"control", "isochronous", "bulk", "interrupt"}; return str[t]; } #endif #ifdef __cplusplus - } +} #endif #endif // TUSB_TYPES_H_ -- cgit v1.3.1 From ff0683d5f827a0db546f026a16abfa48b8dc67e1 Mon Sep 17 00:00:00 2001 From: TenGui Date: Thu, 23 Jul 2026 15:59:12 -0700 Subject: save without formatting --- src/common/tusb_types.h | 416 ++++++++++++++++++++++-------------------------- 1 file changed, 193 insertions(+), 223 deletions(-) (limited to 'src') diff --git a/src/common/tusb_types.h b/src/common/tusb_types.h index 5a91d6182..d0796ccc8 100644 --- a/src/common/tusb_types.h +++ b/src/common/tusb_types.h @@ -13,49 +13,43 @@ #include "tusb_compiler.h" #ifdef __cplusplus -extern "C" { + extern "C" { #endif //------------- Device DCache declaration -------------// -#define TUD_EPBUF_DCACHE_SIZE(_size) \ - (CFG_TUD_MEM_DCACHE_ENABLE ? (TU_DIV_CEIL(_size, CFG_TUD_MEM_DCACHE_LINE_SIZE) * CFG_TUD_MEM_DCACHE_LINE_SIZE) \ - : (_size)) +#define TUD_EPBUF_DCACHE_SIZE(_size) (CFG_TUD_MEM_DCACHE_ENABLE ? \ + (TU_DIV_CEIL(_size, CFG_TUD_MEM_DCACHE_LINE_SIZE) * CFG_TUD_MEM_DCACHE_LINE_SIZE) : (_size)) // Declare an endpoint buffer with uint8_t[size] -#define TUD_EPBUF_DEF(_name, _size) \ - union { \ - CFG_TUD_MEM_ALIGN uint8_t _name[_size]; \ - TU_ATTR_ALIGNED(CFG_TUD_MEM_DCACHE_ENABLE ? CFG_TUD_MEM_DCACHE_LINE_SIZE : 1) \ - uint8_t _name##_dcache_padding[TUD_EPBUF_DCACHE_SIZE(_size)]; \ +#define TUD_EPBUF_DEF(_name, _size) \ + union { \ + CFG_TUD_MEM_ALIGN uint8_t _name[_size]; \ + TU_ATTR_ALIGNED(CFG_TUD_MEM_DCACHE_ENABLE ? CFG_TUD_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUD_EPBUF_DCACHE_SIZE(_size)]; \ } // Declare an endpoint buffer with a type -#define TUD_EPBUF_TYPE_DEF(_type, _name) \ - union { \ - CFG_TUD_MEM_ALIGN _type _name; \ - TU_ATTR_ALIGNED(CFG_TUD_MEM_DCACHE_ENABLE ? CFG_TUD_MEM_DCACHE_LINE_SIZE : 1) \ - uint8_t _name##_dcache_padding[TUD_EPBUF_DCACHE_SIZE(sizeof(_type))]; \ +#define TUD_EPBUF_TYPE_DEF(_type, _name) \ + union { \ + CFG_TUD_MEM_ALIGN _type _name; \ + TU_ATTR_ALIGNED(CFG_TUD_MEM_DCACHE_ENABLE ? CFG_TUD_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUD_EPBUF_DCACHE_SIZE(sizeof(_type))]; \ } //------------- Host DCache declaration -------------// -#define TUH_EPBUF_DCACHE_SIZE(_size) \ - (CFG_TUH_MEM_DCACHE_ENABLE ? (TU_DIV_CEIL(_size, CFG_TUH_MEM_DCACHE_LINE_SIZE) * CFG_TUH_MEM_DCACHE_LINE_SIZE) \ - : (_size)) +#define TUH_EPBUF_DCACHE_SIZE(_size) (CFG_TUH_MEM_DCACHE_ENABLE ? \ + (TU_DIV_CEIL(_size, CFG_TUH_MEM_DCACHE_LINE_SIZE) * CFG_TUH_MEM_DCACHE_LINE_SIZE) : (_size)) // Declare an endpoint buffer with uint8_t[size] -#define TUH_EPBUF_DEF(_name, _size) \ - union { \ - CFG_TUH_MEM_ALIGN uint8_t _name[_size]; \ - TU_ATTR_ALIGNED(CFG_TUH_MEM_DCACHE_ENABLE ? CFG_TUH_MEM_DCACHE_LINE_SIZE : 1) \ - uint8_t _name##_dcache_padding[TUH_EPBUF_DCACHE_SIZE(_size)]; \ +#define TUH_EPBUF_DEF(_name, _size) \ + union { \ + CFG_TUH_MEM_ALIGN uint8_t _name[_size]; \ + TU_ATTR_ALIGNED(CFG_TUH_MEM_DCACHE_ENABLE ? CFG_TUH_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUH_EPBUF_DCACHE_SIZE(_size)]; \ } // Declare an endpoint buffer with a type -#define TUH_EPBUF_TYPE_DEF(_type, _name) \ - union { \ - CFG_TUH_MEM_ALIGN _type _name; \ - TU_ATTR_ALIGNED(CFG_TUH_MEM_DCACHE_ENABLE ? CFG_TUH_MEM_DCACHE_LINE_SIZE : 1) \ - uint8_t _name##_dcache_padding[TUH_EPBUF_DCACHE_SIZE(sizeof(_type))]; \ +#define TUH_EPBUF_TYPE_DEF(_type, _name) \ + union { \ + CFG_TUH_MEM_ALIGN _type _name; \ + TU_ATTR_ALIGNED(CFG_TUH_MEM_DCACHE_ENABLE ? CFG_TUH_MEM_DCACHE_LINE_SIZE : 1) uint8_t _name##_dcache_padding[TUH_EPBUF_DCACHE_SIZE(sizeof(_type))]; \ } @@ -71,10 +65,10 @@ typedef enum { /// defined base on EHCI specs value for Endpoint Speed typedef enum { - TUSB_SPEED_FULL = 0, - TUSB_SPEED_LOW = 1, - TUSB_SPEED_HIGH = 2, - TUSB_SPEED_AUTO = 0xaa, + TUSB_SPEED_FULL = 0, + TUSB_SPEED_LOW = 1, + TUSB_SPEED_HIGH = 2, + TUSB_SPEED_AUTO = 0xaa, TUSB_SPEED_INVALID = 0xff, } tusb_speed_t; @@ -105,18 +99,18 @@ enum { }; // Endpoint Bulk size depending on host/device max speed -#define TUD_EPSIZE_BULK_MAX (TUD_OPT_HIGH_SPEED ? 512 : 64) -#define TUH_EPSIZE_BULK_MAX (TUH_OPT_HIGH_SPEED ? 512 : 64) +#define TUD_EPSIZE_BULK_MAX (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define TUH_EPSIZE_BULK_MAX (TUH_OPT_HIGH_SPEED ? 512 : 64) /// Isochronous Endpoint Attributes typedef enum { - TUSB_ISO_EP_ATT_NO_SYNC = 0x00, - TUSB_ISO_EP_ATT_ASYNCHRONOUS = 0x04, - TUSB_ISO_EP_ATT_ADAPTIVE = 0x08, - TUSB_ISO_EP_ATT_SYNCHRONOUS = 0x0C, - TUSB_ISO_EP_ATT_DATA = 0x00, ///< Data End Point - TUSB_ISO_EP_ATT_EXPLICIT_FB = 0x10, ///< Feedback End Point - TUSB_ISO_EP_ATT_IMPLICIT_FB = 0x20, ///< Data endpoint that also serves as an implicit feedback + TUSB_ISO_EP_ATT_NO_SYNC = 0x00, + TUSB_ISO_EP_ATT_ASYNCHRONOUS = 0x04, + TUSB_ISO_EP_ATT_ADAPTIVE = 0x08, + TUSB_ISO_EP_ATT_SYNCHRONOUS = 0x0C, + TUSB_ISO_EP_ATT_DATA = 0x00, ///< Data End Point + TUSB_ISO_EP_ATT_EXPLICIT_FB = 0x10, ///< Feedback End Point + TUSB_ISO_EP_ATT_IMPLICIT_FB = 0x20, ///< Data endpoint that also serves as an implicit feedback } tusb_iso_ep_attribute_t; /// USB Descriptor Types @@ -133,35 +127,35 @@ typedef enum { TUSB_DESC_DEBUG = 0x0A, TUSB_DESC_INTERFACE_ASSOCIATION = 0x0B, - TUSB_DESC_BOS = 0x0F, - TUSB_DESC_DEVICE_CAPABILITY = 0x10, + TUSB_DESC_BOS = 0x0F, + TUSB_DESC_DEVICE_CAPABILITY = 0x10, - TUSB_DESC_FUNCTIONAL = 0x21, + TUSB_DESC_FUNCTIONAL = 0x21, // Class Specific Descriptor - TUSB_DESC_CS_DEVICE = 0x21, - TUSB_DESC_CS_CONFIGURATION = 0x22, - TUSB_DESC_CS_STRING = 0x23, - TUSB_DESC_CS_INTERFACE = 0x24, - TUSB_DESC_CS_ENDPOINT = 0x25, + TUSB_DESC_CS_DEVICE = 0x21, + TUSB_DESC_CS_CONFIGURATION = 0x22, + TUSB_DESC_CS_STRING = 0x23, + TUSB_DESC_CS_INTERFACE = 0x24, + TUSB_DESC_CS_ENDPOINT = 0x25, TUSB_DESC_SUPERSPEED_ENDPOINT_COMPANION = 0x30, TUSB_DESC_SUPERSPEED_ISO_ENDPOINT_COMPANION = 0x31 } tusb_desc_type_t; typedef enum { - TUSB_REQ_GET_STATUS = 0, - TUSB_REQ_CLEAR_FEATURE = 1, - TUSB_REQ_RESERVED = 2, - TUSB_REQ_SET_FEATURE = 3, - TUSB_REQ_RESERVED2 = 4, - TUSB_REQ_SET_ADDRESS = 5, - TUSB_REQ_GET_DESCRIPTOR = 6, - TUSB_REQ_SET_DESCRIPTOR = 7, - TUSB_REQ_GET_CONFIGURATION = 8, - TUSB_REQ_SET_CONFIGURATION = 9, - TUSB_REQ_GET_INTERFACE = 10, - TUSB_REQ_SET_INTERFACE = 11, + TUSB_REQ_GET_STATUS = 0 , + TUSB_REQ_CLEAR_FEATURE = 1 , + TUSB_REQ_RESERVED = 2 , + TUSB_REQ_SET_FEATURE = 3 , + TUSB_REQ_RESERVED2 = 4 , + TUSB_REQ_SET_ADDRESS = 5 , + TUSB_REQ_GET_DESCRIPTOR = 6 , + TUSB_REQ_SET_DESCRIPTOR = 7 , + TUSB_REQ_GET_CONFIGURATION = 8 , + TUSB_REQ_SET_CONFIGURATION = 9 , + TUSB_REQ_GET_INTERFACE = 10 , + TUSB_REQ_SET_INTERFACE = 11 , TUSB_REQ_SYNCH_FRAME = 12 } tusb_request_code_t; @@ -179,7 +173,7 @@ typedef enum { } tusb_request_type_t; typedef enum { - TUSB_REQ_RCPT_DEVICE = 0, + TUSB_REQ_RCPT_DEVICE =0, TUSB_REQ_RCPT_INTERFACE, TUSB_REQ_RCPT_ENDPOINT, TUSB_REQ_RCPT_OTHER @@ -187,41 +181,42 @@ typedef enum { // https://www.usb.org/defined-class-codes typedef enum { - TUSB_CLASS_UNSPECIFIED = 0, - TUSB_CLASS_AUDIO = 1, - TUSB_CLASS_CDC = 2, - TUSB_CLASS_HID = 3, - TUSB_CLASS_RESERVED_4 = 4, - TUSB_CLASS_PHYSICAL = 5, - TUSB_CLASS_IMAGE = 6, - TUSB_CLASS_PRINTER = 7, - TUSB_CLASS_MSC = 8, - TUSB_CLASS_HUB = 9, - TUSB_CLASS_CDC_DATA = 10, - TUSB_CLASS_SMART_CARD = 11, - TUSB_CLASS_RESERVED_12 = 12, - TUSB_CLASS_CONTENT_SECURITY = 13, - TUSB_CLASS_VIDEO = 14, - TUSB_CLASS_PERSONAL_HEALTHCARE = 15, - TUSB_CLASS_AUDIO_VIDEO = 16, - - TUSB_CLASS_DIAGNOSTIC = 0xDC, - TUSB_CLASS_WIRELESS_CONTROLLER = 0xE0, - TUSB_CLASS_MISC = 0xEF, - TUSB_CLASS_APPLICATION_SPECIFIC = 0xFE, + TUSB_CLASS_UNSPECIFIED = 0 , + TUSB_CLASS_AUDIO = 1 , + TUSB_CLASS_CDC = 2 , + TUSB_CLASS_HID = 3 , + TUSB_CLASS_RESERVED_4 = 4 , + TUSB_CLASS_PHYSICAL = 5 , + TUSB_CLASS_IMAGE = 6 , + TUSB_CLASS_PRINTER = 7 , + TUSB_CLASS_MSC = 8 , + TUSB_CLASS_HUB = 9 , + TUSB_CLASS_CDC_DATA = 10 , + TUSB_CLASS_SMART_CARD = 11 , + TUSB_CLASS_RESERVED_12 = 12 , + TUSB_CLASS_CONTENT_SECURITY = 13 , + TUSB_CLASS_VIDEO = 14 , + TUSB_CLASS_PERSONAL_HEALTHCARE = 15 , + TUSB_CLASS_AUDIO_VIDEO = 16 , + + TUSB_CLASS_DIAGNOSTIC = 0xDC , + TUSB_CLASS_WIRELESS_CONTROLLER = 0xE0 , + TUSB_CLASS_MISC = 0xEF , + TUSB_CLASS_APPLICATION_SPECIFIC = 0xFE , TUSB_CLASS_VENDOR_SPECIFIC = 0xFF } tusb_class_code_t; -typedef enum { +typedef enum +{ MISC_SUBCLASS_COMMON = 2 -} misc_subclass_type_t; +}misc_subclass_type_t; typedef enum { MISC_PROTOCOL_IAD = 1 } misc_protocol_type_t; typedef enum { - APP_SUBCLASS_USBTMC = 0x03, + APP_SUBCLASS_USBTMC = 0x03, APP_SUBCLASS_DFU_RUNTIME = 0x01 } app_subclass_type_t; @@ -249,14 +244,14 @@ enum { TUSB_DESC_CONFIG_ATT_SELF_POWERED = 1 << 6, }; -#define TUSB_DESC_CONFIG_POWER_MA(x) (uint8_t)((x) / 2) +#define TUSB_DESC_CONFIG_POWER_MA(x) (uint8_t)((x)/2) // USB 2.0 Spec Table 9-7: Test Mode Selectors typedef enum { - TUSB_FEATURE_TEST_J = 1, - TUSB_FEATURE_TEST_K = 2, - TUSB_FEATURE_TEST_SE0_NAK = 3, - TUSB_FEATURE_TEST_PACKET = 4, + TUSB_FEATURE_TEST_J = 1, + TUSB_FEATURE_TEST_K = 2, + TUSB_FEATURE_TEST_SE0_NAK = 3, + TUSB_FEATURE_TEST_PACKET = 4, TUSB_FEATURE_TEST_FORCE_ENABLE = 5, } tusb_feature_test_mode_t; @@ -276,8 +271,8 @@ typedef enum { // TODO remove enum { - DESC_OFFSET_LEN = 0, - DESC_OFFSET_TYPE = 1, + DESC_OFFSET_LEN = 0, + DESC_OFFSET_TYPE = 1, DESC_OFFSET_SUBTYPE = 2 }; @@ -309,8 +304,8 @@ enum { }; enum { - TU_EP0_OUT = 0x00, - TU_EP0_IN = 0x80 + TU_EP0_OUT = 0x00, + TU_EP0_IN = 0x80 }; @@ -318,7 +313,7 @@ enum { // //--------------------------------------------------------------------+ typedef struct { - tusb_role_t role; + tusb_role_t role; tusb_speed_t speed; } tusb_rhport_init_t; @@ -337,101 +332,77 @@ TU_ATTR_BIT_FIELD_ORDER_BEGIN /// USB Device Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength; ///< Size of this descriptor in bytes. - uint8_t bDescriptorType; ///< DEVICE Descriptor Type. - uint16_t bcdUSB; ///< BUSB Specification Release Number in Binary-Coded Decimal (i.e., 2.10 is 210H). - uint8_t bDeviceClass; ///< Class code (assigned by the USB-IF). - uint8_t bDeviceSubClass; ///< Subclass code (assigned by the USB-IF). - uint8_t bDeviceProtocol; ///< Protocol code (assigned by the USB-IF). - uint8_t bMaxPacketSize0; ///< Maximum packet size for endpoint zero (only 8, 16, 32, or 64 are valid). For HS devices - ///< is fixed to 64. - uint16_t idVendor; ///< Vendor ID (assigned by the USB-IF). - uint16_t idProduct; ///< Product ID (assigned by the manufacturer). - uint16_t bcdDevice; ///< Device release number in binary-coded decimal. - uint8_t iManufacturer; ///< Index of string descriptor describing manufacturer. - uint8_t iProduct; ///< Index of string descriptor describing product. - uint8_t iSerialNumber; ///< Index of string descriptor describing the device's serial number. - uint8_t bNumConfigurations; ///< Number of possible configurations. + uint8_t bLength ; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType ; ///< DEVICE Descriptor Type. + uint16_t bcdUSB ; ///< BUSB Specification Release Number in Binary-Coded Decimal (i.e., 2.10 is 210H). + uint8_t bDeviceClass ; ///< Class code (assigned by the USB-IF). + uint8_t bDeviceSubClass ; ///< Subclass code (assigned by the USB-IF). + uint8_t bDeviceProtocol ; ///< Protocol code (assigned by the USB-IF). + uint8_t bMaxPacketSize0 ; ///< Maximum packet size for endpoint zero (only 8, 16, 32, or 64 are valid). For HS devices is fixed to 64. + uint16_t idVendor ; ///< Vendor ID (assigned by the USB-IF). + uint16_t idProduct ; ///< Product ID (assigned by the manufacturer). + uint16_t bcdDevice ; ///< Device release number in binary-coded decimal. + uint8_t iManufacturer ; ///< Index of string descriptor describing manufacturer. + uint8_t iProduct ; ///< Index of string descriptor describing product. + uint8_t iSerialNumber ; ///< Index of string descriptor describing the device's serial number. + uint8_t bNumConfigurations ; ///< Number of possible configurations. } tusb_desc_device_t; -TU_VERIFY_STATIC(sizeof(tusb_desc_device_t) == 18u, "size is not correct"); +TU_VERIFY_STATIC( sizeof(tusb_desc_device_t) == 18u, "size is not correct"); // USB Binary Device Object Store (BOS) Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength; ///< Size of this descriptor in bytes - uint8_t bDescriptorType; ///< CONFIGURATION Descriptor Type - uint16_t wTotalLength; ///< Total length of data returned for this descriptor - uint8_t bNumDeviceCaps; ///< Number of device capability descriptors in the BOS + uint8_t bLength ; ///< Size of this descriptor in bytes + uint8_t bDescriptorType ; ///< CONFIGURATION Descriptor Type + uint16_t wTotalLength ; ///< Total length of data returned for this descriptor + uint8_t bNumDeviceCaps ; ///< Number of device capability descriptors in the BOS } tusb_desc_bos_t; -TU_VERIFY_STATIC(sizeof(tusb_desc_bos_t) == 5u, "size is not correct"); +TU_VERIFY_STATIC( sizeof(tusb_desc_bos_t) == 5u, "size is not correct"); /// USB Configuration Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength; ///< Size of this descriptor in bytes - uint8_t bDescriptorType; ///< CONFIGURATION Descriptor Type - uint16_t wTotalLength; ///< Total length of data returned for this configuration. Includes the combined length of all - ///< descriptors (configuration, interface, endpoint, and class- or vendor-specific) returned - ///< for this configuration. - - uint8_t bNumInterfaces; ///< Number of interfaces supported by this configuration - uint8_t bConfigurationValue; ///< Value to use as an argument to the SetConfiguration() request to select this - ///< configuration. - uint8_t iConfiguration; ///< Index of string descriptor describing this configuration - uint8_t bmAttributes; ///< Configuration characteristics \n D7: Reserved (set to one)\n D6: Self-powered \n D5: Remote - ///< Wakeup \n D4...0: Reserved (reset to zero) \n D7 is reserved and must be set to one for - ///< historical reasons. \n A device configuration that uses power from the bus and a local - ///< source reports a non-zero value in bMaxPower to indicate the amount of bus power required - ///< and sets D6. The actual power source at runtime may be determined using the - ///< GetStatus(DEVICE) request (see USB 2.0 spec Section 9.4.5). \n If a device configuration - ///< supports remote wakeup, D5 is set to one. - uint8_t bMaxPower; ///< Maximum power consumption of the USB device from the bus in this specific configuration when - ///< the device is fully operational. Expressed in 2 mA units (i.e., 50 = 100 mA). + uint8_t bLength ; ///< Size of this descriptor in bytes + uint8_t bDescriptorType ; ///< CONFIGURATION Descriptor Type + uint16_t wTotalLength ; ///< Total length of data returned for this configuration. Includes the combined length of all descriptors (configuration, interface, endpoint, and class- or vendor-specific) returned for this configuration. + + uint8_t bNumInterfaces ; ///< Number of interfaces supported by this configuration + uint8_t bConfigurationValue ; ///< Value to use as an argument to the SetConfiguration() request to select this configuration. + uint8_t iConfiguration ; ///< Index of string descriptor describing this configuration + uint8_t bmAttributes ; ///< Configuration characteristics \n D7: Reserved (set to one)\n D6: Self-powered \n D5: Remote Wakeup \n D4...0: Reserved (reset to zero) \n D7 is reserved and must be set to one for historical reasons. \n A device configuration that uses power from the bus and a local source reports a non-zero value in bMaxPower to indicate the amount of bus power required and sets D6. The actual power source at runtime may be determined using the GetStatus(DEVICE) request (see USB 2.0 spec Section 9.4.5). \n If a device configuration supports remote wakeup, D5 is set to one. + uint8_t bMaxPower ; ///< Maximum power consumption of the USB device from the bus in this specific configuration when the device is fully operational. Expressed in 2 mA units (i.e., 50 = 100 mA). } tusb_desc_configuration_t; -TU_VERIFY_STATIC(sizeof(tusb_desc_configuration_t) == 9u, "size is not correct"); +TU_VERIFY_STATIC( sizeof(tusb_desc_configuration_t) == 9u, "size is not correct"); /// USB Interface Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength; ///< Size of this descriptor in bytes - uint8_t bDescriptorType; ///< INTERFACE Descriptor Type - - uint8_t bInterfaceNumber; ///< Number of this interface. Zero-based value identifying the index in the array of - ///< concurrent interfaces supported by this configuration. - uint8_t - bAlternateSetting; ///< Value used to select this alternate setting for the interface identified in the prior field - uint8_t bNumEndpoints; ///< Number of endpoints used by this interface (excluding endpoint zero). If this value is - ///< zero, this interface only uses the Default Control Pipe. - uint8_t bInterfaceClass; ///< Class code (assigned by the USB-IF). \li A value of zero is reserved for future - ///< standardization. \li If this field is set to FFH, the interface class is - ///< vendor-specific. \li All other values are reserved for assignment by the USB-IF. - uint8_t bInterfaceSubClass; ///< Subclass code (assigned by the USB-IF). \n These codes are qualified by the value of - ///< the bInterfaceClass field. \li If the bInterfaceClass field is reset to zero, this - ///< field must also be reset to zero. \li If the bInterfaceClass field is not set to FFH, - ///< all values are reserved for assignment by the USB-IF. - uint8_t bInterfaceProtocol; ///< Protocol code (assigned by the USB). \n These codes are qualified by the value of the - ///< bInterfaceClass and the bInterfaceSubClass fields. If an interface supports - ///< class-specific requests, this code identifies the protocols that the device uses as - ///< defined by the specification of the device class. \li If this field is reset to zero, - ///< the device does not use a class-specific protocol on this interface. \li If this - ///< field is set to FFH, the device uses a vendor-specific protocol for this interface. - uint8_t iInterface; ///< Index of string descriptor describing this interface + uint8_t bLength ; ///< Size of this descriptor in bytes + uint8_t bDescriptorType ; ///< INTERFACE Descriptor Type + + uint8_t bInterfaceNumber ; ///< Number of this interface. Zero-based value identifying the index in the array of concurrent interfaces supported by this configuration. + uint8_t bAlternateSetting ; ///< Value used to select this alternate setting for the interface identified in the prior field + uint8_t bNumEndpoints ; ///< Number of endpoints used by this interface (excluding endpoint zero). If this value is zero, this interface only uses the Default Control Pipe. + uint8_t bInterfaceClass ; ///< Class code (assigned by the USB-IF). \li A value of zero is reserved for future standardization. \li If this field is set to FFH, the interface class is vendor-specific. \li All other values are reserved for assignment by the USB-IF. + uint8_t bInterfaceSubClass ; ///< Subclass code (assigned by the USB-IF). \n These codes are qualified by the value of the bInterfaceClass field. \li If the bInterfaceClass field is reset to zero, this field must also be reset to zero. \li If the bInterfaceClass field is not set to FFH, all values are reserved for assignment by the USB-IF. + uint8_t bInterfaceProtocol ; ///< Protocol code (assigned by the USB). \n These codes are qualified by the value of the bInterfaceClass and the bInterfaceSubClass fields. If an interface supports class-specific requests, this code identifies the protocols that the device uses as defined by the specification of the device class. \li If this field is reset to zero, the device does not use a class-specific protocol on this interface. \li If this field is set to FFH, the device uses a vendor-specific protocol for this interface. + uint8_t iInterface ; ///< Index of string descriptor describing this interface } tusb_desc_interface_t; -TU_VERIFY_STATIC(sizeof(tusb_desc_interface_t) == 9u, "size is not correct"); +TU_VERIFY_STATIC( sizeof(tusb_desc_interface_t) == 9u, "size is not correct"); /// USB Endpoint Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength; // Size of this descriptor in bytes - uint8_t bDescriptorType; // ENDPOINT Descriptor Type + uint8_t bLength ; // Size of this descriptor in bytes + uint8_t bDescriptorType ; // ENDPOINT Descriptor Type - uint8_t bEndpointAddress; // The address of the endpoint + uint8_t bEndpointAddress ; // The address of the endpoint struct TU_ATTR_PACKED { #if (TU_BITFIELD_ORDER == TU_BITFIELD_LE) - uint8_t xfer : 2; // Control, ISO, Bulk, Interrupt - uint8_t sync : 2; // None, Asynchronous, Adaptive, Synchronous - uint8_t usage : 2; // Data, Feedback, Implicit feedback + uint8_t xfer : 2; // Control, ISO, Bulk, Interrupt + uint8_t sync : 2; // None, Asynchronous, Adaptive, Synchronous + uint8_t usage : 2; // Data, Feedback, Implicit feedback uint8_t : 2; #elif (TU_BITFIELD_ORDER == TU_BITFIELD_BE) uint8_t : 2; @@ -443,70 +414,70 @@ typedef struct TU_ATTR_PACKED { #endif } bmAttributes; - uint16_t wMaxPacketSize; // Bit 10..0 : max packet size, bit 12..11 additional transaction per highspeed micro-frame - uint8_t bInterval; // Polling interval, in frames or microframes depending on the operating speed + uint16_t wMaxPacketSize ; // Bit 10..0 : max packet size, bit 12..11 additional transaction per highspeed micro-frame + uint8_t bInterval ; // Polling interval, in frames or microframes depending on the operating speed } tusb_desc_endpoint_t; -TU_VERIFY_STATIC(sizeof(tusb_desc_endpoint_t) == 7u, "size is not correct"); +TU_VERIFY_STATIC( sizeof(tusb_desc_endpoint_t) == 7u, "size is not correct"); /// USB Other Speed Configuration Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength; ///< Size of descriptor - uint8_t bDescriptorType; ///< Other_speed_Configuration Type - uint16_t wTotalLength; ///< Total length of data returned - - uint8_t bNumInterfaces; ///< Number of interfaces supported by this speed configuration - uint8_t bConfigurationValue; ///< Value to use to select configuration - uint8_t iConfiguration; ///< Index of string descriptor - uint8_t bmAttributes; ///< Same as Configuration descriptor - uint8_t bMaxPower; ///< Same as Configuration descriptor + uint8_t bLength ; ///< Size of descriptor + uint8_t bDescriptorType ; ///< Other_speed_Configuration Type + uint16_t wTotalLength ; ///< Total length of data returned + + uint8_t bNumInterfaces ; ///< Number of interfaces supported by this speed configuration + uint8_t bConfigurationValue ; ///< Value to use to select configuration + uint8_t iConfiguration ; ///< Index of string descriptor + uint8_t bmAttributes ; ///< Same as Configuration descriptor + uint8_t bMaxPower ; ///< Same as Configuration descriptor } tusb_desc_other_speed_t; /// USB Device Qualifier Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength; ///< Size of descriptor - uint8_t bDescriptorType; ///< Device Qualifier Type - uint16_t bcdUSB; ///< USB specification version number (e.g., 0200H for V2.00) + uint8_t bLength ; ///< Size of descriptor + uint8_t bDescriptorType ; ///< Device Qualifier Type + uint16_t bcdUSB ; ///< USB specification version number (e.g., 0200H for V2.00) - uint8_t bDeviceClass; ///< Class Code - uint8_t bDeviceSubClass; ///< SubClass Code - uint8_t bDeviceProtocol; ///< Protocol Code + uint8_t bDeviceClass ; ///< Class Code + uint8_t bDeviceSubClass ; ///< SubClass Code + uint8_t bDeviceProtocol ; ///< Protocol Code - uint8_t bMaxPacketSize0; ///< Maximum packet size for other speed - uint8_t bNumConfigurations; ///< Number of Other-speed Configurations - uint8_t bReserved; ///< Reserved for future use, must be zero + uint8_t bMaxPacketSize0 ; ///< Maximum packet size for other speed + uint8_t bNumConfigurations ; ///< Number of Other-speed Configurations + uint8_t bReserved ; ///< Reserved for future use, must be zero } tusb_desc_device_qualifier_t; -TU_VERIFY_STATIC(sizeof(tusb_desc_device_qualifier_t) == 10u, "size is not correct"); +TU_VERIFY_STATIC( sizeof(tusb_desc_device_qualifier_t) == 10u, "size is not correct"); /// USB Interface Association Descriptor (IAD ECN) typedef struct TU_ATTR_PACKED { - uint8_t bLength; ///< Size of descriptor - uint8_t bDescriptorType; ///< Other_speed_Configuration Type + uint8_t bLength ; ///< Size of descriptor + uint8_t bDescriptorType ; ///< Other_speed_Configuration Type - uint8_t bFirstInterface; ///< Index of the first associated interface. - uint8_t bInterfaceCount; ///< Total number of associated interfaces. + uint8_t bFirstInterface ; ///< Index of the first associated interface. + uint8_t bInterfaceCount ; ///< Total number of associated interfaces. - uint8_t bFunctionClass; ///< Interface class ID. - uint8_t bFunctionSubClass; ///< Interface subclass ID. - uint8_t bFunctionProtocol; ///< Interface protocol ID. + uint8_t bFunctionClass ; ///< Interface class ID. + uint8_t bFunctionSubClass ; ///< Interface subclass ID. + uint8_t bFunctionProtocol ; ///< Interface protocol ID. - uint8_t iFunction; ///< Index of the string descriptor describing the interface association. + uint8_t iFunction ; ///< Index of the string descriptor describing the interface association. } tusb_desc_interface_assoc_t; -TU_VERIFY_STATIC(sizeof(tusb_desc_interface_assoc_t) == 8u, "size is not correct"); +TU_VERIFY_STATIC( sizeof(tusb_desc_interface_assoc_t) == 8u, "size is not correct"); // USB String Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength; ///< Size of this descriptor in bytes - uint8_t bDescriptorType; ///< Descriptor Type + uint8_t bLength ; ///< Size of this descriptor in bytes + uint8_t bDescriptorType ; ///< Descriptor Type uint16_t utf16le[]; } tusb_desc_string_t; // USB Binary Device Object Store (BOS) typedef struct TU_ATTR_PACKED { uint8_t bLength; - uint8_t bDescriptorType; + uint8_t bDescriptorType ; uint8_t bDevCapabilityType; uint8_t bReserved; uint8_t PlatformCapabilityUUID[16]; @@ -523,8 +494,8 @@ typedef struct TU_ATTR_PACKED { // DFU Functional Descriptor typedef struct TU_ATTR_PACKED { - uint8_t bLength; - uint8_t bDescriptorType; + uint8_t bLength; + uint8_t bDescriptorType; union { struct TU_ATTR_PACKED { @@ -551,13 +522,13 @@ typedef struct TU_ATTR_PACKED { union { struct TU_ATTR_PACKED { #if (TU_BITFIELD_ORDER == TU_BITFIELD_LE) - uint8_t recipient : 5; ///< Recipient type tusb_request_recipient_t. - uint8_t type : 2; ///< Request type tusb_request_type_t. - uint8_t direction : 1; ///< Direction type. tusb_dir_t + uint8_t recipient : 5; ///< Recipient type tusb_request_recipient_t. + uint8_t type : 2; ///< Request type tusb_request_type_t. + uint8_t direction : 1; ///< Direction type. tusb_dir_t #elif (TU_BITFIELD_ORDER == TU_BITFIELD_BE) - uint8_t direction : 1; ///< Direction type. tusb_dir_t - uint8_t type : 2; ///< Request type tusb_request_type_t. - uint8_t recipient : 5; ///< Recipient type tusb_request_recipient_t. + uint8_t direction : 1; ///< Direction type. tusb_dir_t + uint8_t type : 2; ///< Request type tusb_request_type_t. + uint8_t recipient : 5; ///< Recipient type tusb_request_recipient_t. #else #error "Please define TU_BITFIELD_ORDER as TU_BITFIELD_LE or TU_BITFIELD_BE" #endif @@ -572,43 +543,42 @@ typedef struct TU_ATTR_PACKED { uint16_t wLength; } tusb_control_request_t; -TU_VERIFY_STATIC(sizeof(tusb_control_request_t) == 8u, "size is not correct"); +TU_VERIFY_STATIC( sizeof(tusb_control_request_t) == 8u, "size is not correct"); -TU_ATTR_PACKED_END // End of all packed definitions - TU_ATTR_BIT_FIELD_ORDER_END +TU_ATTR_PACKED_END // End of all packed definitions +TU_ATTR_BIT_FIELD_ORDER_END - //--------------------------------------------------------------------+ - // Endpoint helper - //--------------------------------------------------------------------+ +//--------------------------------------------------------------------+ +// Endpoint helper +//--------------------------------------------------------------------+ - // Get direction from Endpoint address - TU_ATTR_ALWAYS_INLINE static inline tusb_dir_t - tu_edpt_dir(uint8_t addr) { +// Get direction from Endpoint address +TU_ATTR_ALWAYS_INLINE static inline tusb_dir_t tu_edpt_dir(uint8_t addr) { return (addr & TUSB_DIR_IN_MASK) ? TUSB_DIR_IN : TUSB_DIR_OUT; } // Get Endpoint number from address TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_edpt_number(uint8_t addr) { - return (uint8_t)(addr & TUSB_EPNUM_MASK); + return (uint8_t) (addr & TUSB_EPNUM_MASK); } TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_edpt_addr(uint8_t num, uint8_t dir) { - return (uint8_t)(num | (dir == (uint8_t)TUSB_DIR_IN ? (uint8_t)TUSB_DIR_IN_MASK : 0u)); + return (uint8_t) (num | (dir == (uint8_t)TUSB_DIR_IN ? (uint8_t)TUSB_DIR_IN_MASK : 0u)); } -TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_edpt_packet_size(const tusb_desc_endpoint_t *desc_ep) { +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_edpt_packet_size(tusb_desc_endpoint_t const* desc_ep) { return tu_le16toh(desc_ep->wMaxPacketSize) & 0x7FF; } #if CFG_TUSB_DEBUG TU_ATTR_ALWAYS_INLINE static inline const char *tu_edpt_type_str(tusb_xfer_type_t t) { - const tu_static char *str[] = {"control", "isochronous", "bulk", "interrupt"}; + tu_static const char *str[] = {"control", "isochronous", "bulk", "interrupt"}; return str[t]; } #endif #ifdef __cplusplus -} + } #endif #endif // TUSB_TYPES_H_ -- cgit v1.3.1 From 05f19ab438641823d047c811e7426177788d0490 Mon Sep 17 00:00:00 2001 From: TenGui Date: Sun, 26 Jul 2026 12:16:23 -0700 Subject: TU_MIN suggestion --- src/common/tusb_types.h | 2 +- src/device/usbd.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/common/tusb_types.h b/src/common/tusb_types.h index d0796ccc8..fa4d67df1 100644 --- a/src/common/tusb_types.h +++ b/src/common/tusb_types.h @@ -244,7 +244,7 @@ enum { TUSB_DESC_CONFIG_ATT_SELF_POWERED = 1 << 6, }; -#define TUSB_DESC_CONFIG_POWER_MA(x) (uint8_t)((x)/2) +#define TUSB_DESC_CONFIG_POWER_MA(x) ((uint8_t)TU_MIN((x)/2, UINT8_MAX)) // USB 2.0 Spec Table 9-7: Test Mode Selectors typedef enum { diff --git a/src/device/usbd.h b/src/device/usbd.h index 296ec417d..2015d1869 100644 --- a/src/device/usbd.h +++ b/src/device/usbd.h @@ -229,7 +229,7 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ // Config number, interface count, string index, total length, attribute, power in mA #define TUD_CONFIG_DESCRIPTOR(config_num, _itfcount, _stridx, _total_len, _attribute, _power_ma) \ - 9, TUSB_DESC_CONFIGURATION, U16_TO_U8S_LE(_total_len), _itfcount, config_num, _stridx, TU_BIT(7) | _attribute, (uint8_t)((_power_ma)/2) + 9, TUSB_DESC_CONFIGURATION, U16_TO_U8S_LE(_total_len), _itfcount, config_num, _stridx, TU_BIT(7) | _attribute, (uint8_t)TU_MIN((_power_ma)/2, UINT8_MAX) //--------------------------------------------------------------------+ // CDC Descriptor Templates -- cgit v1.3.1 From a2f4786865e85f9cfe7f58c86fbb9355bbd2d701 Mon Sep 17 00:00:00 2001 From: Zixun LI Date: Mon, 27 Jul 2026 14:39:02 +0200 Subject: portable/chipidea: configure LPC USB0 AHB bursts --- src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h | 14 ++++++++++++++ src/portable/chipidea/ci_hs/dcd_ci_hs.c | 4 ++++ src/portable/chipidea/ci_hs/hcd_ci_hs.c | 4 ++++ 3 files changed, 22 insertions(+) (limited to 'src') diff --git a/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h b/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h index f2061bd7a..dec3a34b1 100644 --- a/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h +++ b/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h @@ -34,4 +34,18 @@ static const ci_hs_controller_t _ci_controller[] = #define CI_HCD_INT_ENABLE(_p) NVIC_EnableIRQ ((IRQn_Type)_ci_controller[_p].irqnum) #define CI_HCD_INT_DISABLE(_p) NVIC_DisableIRQ((IRQn_Type)_ci_controller[_p].irqnum) +enum { + CI_HS_LPC18_43_SBUSCFG_OFFSET = 0x90u, + CI_HS_LPC18_43_AHBBRST_INCR16_UNSPEC = 0x07u, +}; + +TU_ATTR_ALWAYS_INLINE static inline void ci_hs_lpc18_43_set_ahb_burst(uint8_t rhport) { + // USB0 SBUSCFG is at offset 0x90. NXP recommends AHBBRST=0x7: + // INCR16 with non-multiple transfers decomposed into smaller unspecified bursts. + if (rhport == 0) { + volatile uint32_t *sbuscfg = (volatile uint32_t *)(_ci_controller[rhport].reg_base + CI_HS_LPC18_43_SBUSCFG_OFFSET); + *sbuscfg = CI_HS_LPC18_43_AHBBRST_INCR16_UNSPEC; + } +} + #endif diff --git a/src/portable/chipidea/ci_hs/dcd_ci_hs.c b/src/portable/chipidea/ci_hs/dcd_ci_hs.c index fa98d6882..32c701bfa 100644 --- a/src/portable/chipidea/ci_hs/dcd_ci_hs.c +++ b/src/portable/chipidea/ci_hs/dcd_ci_hs.c @@ -237,6 +237,10 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { usbmode |= USBMODE_CM_DEVICE; dcd_reg->USBMODE = usbmode; + #if TU_CHECK_MCU(OPT_MCU_LPC18XX, OPT_MCU_LPC43XX) + ci_hs_lpc18_43_set_ahb_burst(rhport); + #endif + #ifdef CFG_TUD_CI_HS_VBUS_CHARGE dcd_reg->OTGSC = OTGSC_VBUS_CHARGE | OTGSC_OTG_TERMINATION; #else diff --git a/src/portable/chipidea/ci_hs/hcd_ci_hs.c b/src/portable/chipidea/ci_hs/hcd_ci_hs.c index 3cb69acfa..c94ce810f 100644 --- a/src/portable/chipidea/ci_hs/hcd_ci_hs.c +++ b/src/portable/chipidea/ci_hs/hcd_ci_hs.c @@ -82,6 +82,10 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { hcd_reg->USBMODE = USBMODE_CM_HOST; #endif + #if TU_CHECK_MCU(OPT_MCU_LPC18XX, OPT_MCU_LPC43XX) + ci_hs_lpc18_43_set_ahb_burst(rhport); + #endif + #if !TUH_OPT_HIGH_SPEED hcd_reg->PORTSC1 |= PORTSC1_FORCE_FULL_SPEED; #endif -- cgit v1.3.1 From f0a8a1483bd4e89ab3adf40d8c61777a5ddadc7f Mon Sep 17 00:00:00 2001 From: Zixun LI Date: Mon, 27 Jul 2026 14:58:13 +0200 Subject: portable/chipidea: configure i.MX RT AHB bursts --- src/portable/chipidea/ci_hs/ci_hs_imxrt.h | 10 ++++++++++ src/portable/chipidea/ci_hs/dcd_ci_hs.c | 4 +++- src/portable/chipidea/ci_hs/hcd_ci_hs.c | 4 +++- 3 files changed, 16 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/portable/chipidea/ci_hs/ci_hs_imxrt.h b/src/portable/chipidea/ci_hs/ci_hs_imxrt.h index f0f918fe2..601e4d1c9 100644 --- a/src/portable/chipidea/ci_hs/ci_hs_imxrt.h +++ b/src/portable/chipidea/ci_hs/ci_hs_imxrt.h @@ -36,6 +36,16 @@ static const ci_hs_controller_t _ci_controller[] = #define CI_HS_REG(_port) ((ci_hs_regs_t*) _ci_controller[_port].reg_base) +enum { + // INCR16/8/4 followed by an unspecified-length burst for the remainder. + CI_HS_IMXRT_AHBBRST_INCR16_UNSPEC = 0x07u, +}; + +TU_ATTR_ALWAYS_INLINE static inline void ci_hs_imxrt_set_ahb_burst(uint8_t rhport) { + USB_Type *usb = (USB_Type *)_ci_controller[rhport].reg_base; + usb->SBUSCFG = USB_SBUSCFG_AHBBRST(CI_HS_IMXRT_AHBBRST_INCR16_UNSPEC); +} + //------------- DCD -------------// #define CI_DCD_INT_ENABLE(_p) NVIC_EnableIRQ ((IRQn_Type)_ci_controller[_p].irqnum) #define CI_DCD_INT_DISABLE(_p) NVIC_DisableIRQ((IRQn_Type)_ci_controller[_p].irqnum) diff --git a/src/portable/chipidea/ci_hs/dcd_ci_hs.c b/src/portable/chipidea/ci_hs/dcd_ci_hs.c index 32c701bfa..62d75b4d3 100644 --- a/src/portable/chipidea/ci_hs/dcd_ci_hs.c +++ b/src/portable/chipidea/ci_hs/dcd_ci_hs.c @@ -237,7 +237,9 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { usbmode |= USBMODE_CM_DEVICE; dcd_reg->USBMODE = usbmode; - #if TU_CHECK_MCU(OPT_MCU_LPC18XX, OPT_MCU_LPC43XX) + #if CFG_TUSB_MCU == OPT_MCU_MIMXRT1XXX + ci_hs_imxrt_set_ahb_burst(rhport); + #elif TU_CHECK_MCU(OPT_MCU_LPC18XX, OPT_MCU_LPC43XX) ci_hs_lpc18_43_set_ahb_burst(rhport); #endif diff --git a/src/portable/chipidea/ci_hs/hcd_ci_hs.c b/src/portable/chipidea/ci_hs/hcd_ci_hs.c index c94ce810f..0fc8e4d70 100644 --- a/src/portable/chipidea/ci_hs/hcd_ci_hs.c +++ b/src/portable/chipidea/ci_hs/hcd_ci_hs.c @@ -82,7 +82,9 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { hcd_reg->USBMODE = USBMODE_CM_HOST; #endif - #if TU_CHECK_MCU(OPT_MCU_LPC18XX, OPT_MCU_LPC43XX) + #if CFG_TUSB_MCU == OPT_MCU_MIMXRT1XXX + ci_hs_imxrt_set_ahb_burst(rhport); + #elif TU_CHECK_MCU(OPT_MCU_LPC18XX, OPT_MCU_LPC43XX) ci_hs_lpc18_43_set_ahb_burst(rhport); #endif -- cgit v1.3.1 From 9bcc2dc25ceb21a54aedb2a57088943b630fba24 Mon Sep 17 00:00:00 2001 From: Anthony VerBurg Date: Mon, 27 Jul 2026 15:48:59 -0700 Subject: Address Codex feedback on spelling --- src/class/hid/hid.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/class/hid/hid.h b/src/class/hid/hid.h index da1ebd970..a6d1dba15 100644 --- a/src/class/hid/hid.h +++ b/src/class/hid/hid.h @@ -931,7 +931,7 @@ enum { HID_USAGE_DESKTOP_DOCKABLE_DEVICE_PRIMARY_USAGE_PAGE = 0xD2, // DV HID_USAGE_DESKTOP_DOCKABLE_DEVICE_PRIMARY_USAGE_ID = 0xD3, // DV HID_USAGE_DESKTOP_DOCKABLE_DEVICE_DOCKING_STATE = 0xD4, // DF - HID_USAGE_DESKTOP_DOCKABLE_DEVICE_DISPLAY_OCCULSION = 0xD5, // CL + HID_USAGE_DESKTOP_DOCKABLE_DEVICE_DISPLAY_OCCLUSION = 0xD5, // CL HID_USAGE_DESKTOP_DOCKABLE_DEVICE_OBJECT_TYPE = 0xD6, // DV // D7-DF Reserved @@ -1325,7 +1325,7 @@ enum { HID_USAGE_TELEPHONY_PHONE_KEY_D = 0x00BF, // Sel HID_USAGE_TELEPHONY_PHONE_CALL_HISTORY_KEY = 0x00C0, // Sel HID_USAGE_TELEPHONY_PHONE_CALLER_ID_KEY = 0x00C1, // Sel - HID_USAGE_TELEPHONY_PHONE_SETINGS_KEY = 0x00C2, // Sel + HID_USAGE_TELEPHONY_PHONE_SETTINGS_KEY = 0x00C2, // Sel // C3-EF Reserved HID_USAGE_TELEPHONY_HOST_CONTROL = 0x00F0, // OOC @@ -1333,7 +1333,7 @@ enum { HID_USAGE_TELEPHONY_HOST_CALL_ACTIVE = 0x00F2, // OOC HID_USAGE_TELEPHONY_ACTIVATE_HANDSET_AUDIO = 0x00F3, // OOC HID_USAGE_TELEPHONY_RING_TYPE = 0x00F4, // NAry - HID_USAGE_TELEPHONY_REDIABLE_PHONE_NUMBER = 0x00F5, // OOC + HID_USAGE_TELEPHONY_REDIALABLE_PHONE_NUMBER = 0x00F5, // OOC // F6-F7 Reserved HID_USAGE_TELEPHONY_STOP_RING_TONE = 0x00F8, // Sel @@ -1868,6 +1868,10 @@ enum { HID_USAGE_CONSUMER_KEYBOARD_BRIGHTNESS_PREVIOUS = 0x0516, // OSC HID_USAGE_CONSUMER_KEYBOARD_BACKLIGHT_LEVEL_SUGGESTION = 0x0517, // SV // 518-FFFF Reserved + + // For Backwards compatibility to prevent current builds from breaking + HID_USAGE_CONSUMER_BRIGHTNESS_INCREMENT = HID_USAGE_CONSUMER_DISPLAY_BRIGHTNESS_INCREMENT, // RTC + HID_USAGE_CONSUMER_BRIGHTNESS_DECREMENT = HID_USAGE_CONSUMER_DISPLAY_BRIGHTNESS_DECREMENT, // RTC }; /// HID Usage Table: Digitizer Page (0x0D) -- cgit v1.3.1 From a20cf74e6a62f5b833baacfe1198648b237b3e7f Mon Sep 17 00:00:00 2001 From: Zixun LI Date: Tue, 28 Jul 2026 21:15:38 +0200 Subject: portable/dwc2: rewind DMA on ISO IN retry --- src/portable/synopsys/dwc2/dcd_dwc2.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c index 86aa54510..b2f1a93a4 100644 --- a/src/portable/synopsys/dwc2/dcd_dwc2.c +++ b/src/portable/synopsys/dwc2/dcd_dwc2.c @@ -1143,7 +1143,12 @@ static void handle_incomplete_iso_in(uint8_t rhport) { xfer_ctl_t *xfer = XFER_CTL_BASE(epnum, TUSB_DIR_IN); if (xfer->iso_retry > 0) { xfer->iso_retry--; - // Restart ISO transfe: re-write TSIZ and CTL + // Restart ISO transfer: re-write DMA address, TSIZ, and CTL + #if CFG_TUD_DWC2_DMA_ENABLE + if (dma_device_enabled(dwc2)) { + epin->diepdma = (uintptr_t) xfer->buffer; + } + #endif dwc2_ep_tsize_t deptsiz = {.value = 0}; deptsiz.xfer_size = xfer->total_len; deptsiz.packet_count = tu_div_ceil(xfer->total_len, xfer->max_size); -- cgit v1.3.1 From 3e3e9f8a978b274b8fe1f5a9b5fd41a94f928606 Mon Sep 17 00:00:00 2001 From: Zixun LI Date: Wed, 29 Jul 2026 00:34:59 +0200 Subject: class/mtp: preserve final OUT payload before ZLP --- src/class/mtp/mtp_device.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index 7657899ec..275c9f858 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -437,8 +437,11 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t TU_LOG_DRV(" MTP Data %s CB: xferred_bytes=%lu, xferred_len/total_len=%lu/%lu, is_complete=%d\r\n", is_data_in ? "IN" : "OUT", xferred_bytes, p_mtp->xferred_len, p_mtp->total_len, is_complete ? 1 : 0); - // Send/queue ZLP if packet is full-sized but transfer is complete - if (is_complete && xferred_bytes > 0 && !(xferred_bytes & (threshold - 1))) { + // Send/queue ZLP if packet is full-sized but transfer is complete. + // OUT must deliver this final payload to the application before receiving + // its terminating ZLP below. + const bool need_zlp = is_complete && xferred_bytes > 0 && !(xferred_bytes & (threshold - 1)); + if (is_data_in && need_zlp) { TU_LOG_DRV(" queue ZLP\r\n"); TU_VERIFY(usbd_edpt_claim(p_mtp->rhport, ep_addr)); TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, ep_addr, NULL, 0, false)); @@ -466,9 +469,16 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t cb_data.io_container = headerless_packet; cb_data.io_container.payload_bytes = xferred_bytes; } - tud_mtp_data_xfer_cb(&cb_data); + if (xferred_bytes > 0) { + tud_mtp_data_xfer_cb(&cb_data); + } - if (is_complete) { + if (need_zlp) { + TU_LOG_DRV(" queue ZLP\r\n"); + TU_VERIFY(usbd_edpt_claim(p_mtp->rhport, ep_addr)); + TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, ep_addr, NULL, 0, false)); + return true; + } else if (is_complete) { // back to header + payload for response cb_data.io_container = headered_packet; cb_data.io_container.header->len = sizeof(mtp_container_header_t); -- cgit v1.3.1 From 84e938bd7661a862589a92635d77f1b7e4a02484 Mon Sep 17 00:00:00 2001 From: Geurt Vos Date: Wed, 29 Jul 2026 11:36:01 +0200 Subject: rp2xxx: added rp2usb_deinit() to fix 'No spinlocks are available' --- src/portable/raspberrypi/rp2040/dcd_rp2040.c | 3 +++ src/portable/raspberrypi/rp2040/rp2040_usb.c | 4 ++++ src/portable/raspberrypi/rp2040/rp2040_usb.h | 1 + 3 files changed, 8 insertions(+) (limited to 'src') diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c index a0d312b8f..564ded535 100644 --- a/src/portable/raspberrypi/rp2040/dcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/dcd_rp2040.c @@ -387,6 +387,9 @@ bool dcd_deinit(uint8_t rhport) { reset_block(RESETS_RESET_USBCTRL_BITS); unreset_block_wait(RESETS_RESET_USBCTRL_BITS); + // Release allocated resources + rp2usb_deinit(); + return true; } diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c index 5421b9b2b..96b335bd3 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -85,6 +85,10 @@ void rp2usb_init(void) { critical_section_init(&rp2usb_lock); } +void rp2usb_deinit(void) { + critical_section_deinit(&rp2usb_lock); +} + void __tusb_irq_path_func(rp2usb_reset_transfer)(hw_endpoint_t *ep) { ep->state = EPSTATE_IDLE; ep->remaining_len = 0; diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.h b/src/portable/raspberrypi/rp2040/rp2040_usb.h index f4e85764d..e5a54007c 100644 --- a/src/portable/raspberrypi/rp2040/rp2040_usb.h +++ b/src/portable/raspberrypi/rp2040/rp2040_usb.h @@ -147,6 +147,7 @@ extern volatile uint32_t e15_last_sof; #endif void rp2usb_init(void); +void rp2usb_deinit(void); // if usb hardware is in host mode TU_ATTR_ALWAYS_INLINE static inline bool rp2usb_is_host_mode(void) { -- cgit v1.3.1 From 530c6708dd6f863e6ad91bc0e3433f887555999a Mon Sep 17 00:00:00 2001 From: Geurt Vos Date: Wed, 29 Jul 2026 12:00:59 +0200 Subject: also added rp2usb_deinit() call to hcd_deinit() --- src/portable/raspberrypi/rp2040/hcd_rp2040.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 28dc7f93c..a04890835 100644 --- a/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -427,6 +427,10 @@ bool hcd_deinit(uint8_t rhport) { irq_remove_handler(USBCTRL_IRQ, hcd_rp2040_irq); reset_block(RESETS_RESET_USBCTRL_BITS); unreset_block_wait(RESETS_RESET_USBCTRL_BITS); + + // Release allocated resources + rp2usb_deinit(); + return true; } -- cgit v1.3.1 From 15dd3120ac4a9dea0d979dc541ef8e0f52f5aa26 Mon Sep 17 00:00:00 2001 From: Javid Khan Date: Thu, 30 Jul 2026 14:13:50 +0530 Subject: clamp committed video payload size to streaming ep buffer Signed-off-by: Javid Khan --- src/class/video/video_device.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src') diff --git a/src/class/video/video_device.c b/src/class/video/video_device.c index 3797e6b2b..390349f13 100644 --- a/src/class/video/video_device.c +++ b/src/class/video/video_device.c @@ -1145,6 +1145,12 @@ static int handle_video_stm_cs_req(uint8_t rhport, uint8_t stage, TU_VERIFY(_update_streaming_parameters(stm, param), VIDEO_ERROR_INVALID_VALUE_WITHIN_RANGE); /* Set the negotiated value */ stm->max_payload_transfer_size = param->dwMaxPayloadTransferSize; + /* A host may commit before the parameters are fully negotiated, in which case + * _update_streaming_parameters returns early without capping the payload size. + * Clamp here so a bulk stream cannot overrun the endpoint buffer. */ + if (CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE < stm->max_payload_transfer_size) { + stm->max_payload_transfer_size = CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE; + } int ret = tud_video_commit_cb(stm->index_vc, stm->index_vs, param); if (VIDEO_ERROR_NONE == ret) { stm->state = VS_STATE_COMMITTED; -- cgit v1.3.1 From b0738b5949130d5ab175835d72bb793830d09bc9 Mon Sep 17 00:00:00 2001 From: rt-rtos Date: Tue, 4 Aug 2026 21:09:20 +0200 Subject: audio_device: enforce the documented FIFO minimum in the EP-IN flow-control guard The comment above audiod_tx_packet_size() states flow control needs a FIFO of at least 4*Navg, but the guard tests nominal_size[1] <= fifo_depth * 4 - true for any FIFO larger than a quarter packet - instead of nominal_size[1] * 4 <= fifo_depth. As written, flow control engages on FIFOs far below its own documented minimum, where the depth/2 setpoint sits within one packet of empty and the packet_size = 0 branch (a zero-length packet, i.e. an audible 1 ms dropout for audio-class hosts) is reachable from ordinary scheduling jitter rather than only from gross clock deviation. With the guard corrected, undersized FIFOs fall back to the plain min(count, max) path as intended. --- src/class/audio/audio_device.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/class/audio/audio_device.c b/src/class/audio/audio_device.c index 94881521a..bc4c7e544 100644 --- a/src/class/audio/audio_device.c +++ b/src/class/audio/audio_device.c @@ -1841,7 +1841,7 @@ static bool audiod_calc_tx_packet_sz(audiod_function_t *audio) { static uint16_t audiod_tx_packet_size(const uint16_t *nominal_size, uint16_t data_count, uint16_t fifo_depth, uint16_t fifo_threshold, uint16_t max_depth) { // Flow control need a FIFO size of at least 4*Navg - if (nominal_size[1] && nominal_size[1] <= fifo_depth * 4) { + if (nominal_size[1] && nominal_size[1] * 4 <= fifo_depth) { // Use blackout to prioritize normal size packet static int ctrl_blackout = 0; uint16_t packet_size; -- cgit v1.3.1 From cf055c237a93d3308e1670285dfd2b629f0dd8af Mon Sep 17 00:00:00 2001 From: Cedric Van den Bergh Date: Wed, 8 Jul 2026 12:47:03 +0100 Subject: ncm: fix carrier lost on link-state notify collision tud_network_link_state() delivered the NETWORK_CONNECTION notification edge-triggered and fire-once: if a previous notification was still in flight, notification_xmit() returned early and the notification for the new link state was never queued. Because link_is_up is committed before the send, the host could be left reporting a stale carrier state - e.g. a permanent NO-CARRIER after a link up. The notification state was also mutated from both the caller and the notify xfer-completion callback with no serialisation, so on RTOS ports where tud_network_link_state() runs in a task other than tud_task() the two could race. Defer the whole link-state update onto the usbd task, so it can no longer race the completion callback. A collision with an in-flight notification is resolved by re-arming notification_xmit_state and letting the existing completion callback drive it forward on the next xfer completion, rather than adding a separate pending/retry flag. A link toggle does not change the link speed, so strictly only the NETWORK_CONNECTION notification needs (re)sending, but reusing the existing speed-then-connection state machine keeps the fix on a single, already-serialised code path. Closes #3760 --- src/class/net/ncm_device.c | 47 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/class/net/ncm_device.c b/src/class/net/ncm_device.c index 84a524f49..72b592787 100644 --- a/src/class/net/ncm_device.c +++ b/src/class/net/ncm_device.c @@ -800,31 +800,56 @@ static void tud_network_recv_renew_r(uint8_t rhport) { } // tud_network_recv_renew /** - * Set the link state and send notification to host + * usbd-task trampoline for tud_network_link_state(), packing rhport and is_up + * into a single pointer-sized argument. + * + * Runs entirely in the usbd task context, so it cannot race the notify + * xfer-completion callback over the notification state machine. Re-arming + * notification_xmit_state and kicking notification_xmit() (rather than + * sending NETWORK_CONNECTION directly) means a state change that collides + * with an in-flight notification is picked up by the existing completion + * callback instead of being silently dropped - which would otherwise leave + * the host stuck at NO-CARRIER after a link-state change. */ -void tud_network_link_state(uint8_t rhport, bool is_up) { - TU_LOG_DRV("tud_network_link_state(%d, %d)\n", rhport, is_up); +static void ncm_link_state_task(void *param) { + uintptr_t const arg = (uintptr_t) param; + uint8_t const rhport = (uint8_t) (arg >> 1); + bool const is_up = (arg & 1u) != 0; if (ncm_interface.link_is_up == is_up) { - // No change in link state - return; + return; // no change in link state } ncm_interface.link_is_up = is_up; - // Only send notification if we have an active data interface if (ncm_interface.itf_data_alt != 1) { - TU_LOG_DRV(" link state notification skipped (interface not active)\n"); - return; + TU_LOG_DRV(" link state notification deferred (interface not active)\n"); + return; // data interface not active yet; SET_INTERFACE(alt=1) will notify } - // Reset notification state to send speed change notification first, then link state notification + // A link toggle does not change the link speed, so strictly only the + // NETWORK_CONNECTION notification would need (re)sending. Re-running the + // speed-then-connection sequence keeps this on the same state machine the + // completion callback already drives, at the cost of a redundant speed + // notification on every toggle. ncm_interface.notification_xmit_state = NOTIFICATION_SPEED; - - // Trigger notification transmission notification_xmit(rhport, false); } +/** + * Set the link state and notify the host. + * + * Defers onto the usbd task so a caller running in a different task than + * tud_task() cannot race the notification state machine against the notify + * xfer-completion callback. + */ +void tud_network_link_state(uint8_t rhport, bool is_up) { + TU_LOG_DRV("tud_network_link_state(%d, %d)\n", rhport, is_up); + + uintptr_t const arg = ((uintptr_t) rhport << 1) | (is_up ? 1u : 0u); + usbd_defer_func(ncm_link_state_task, (void *) arg, false); +} + //----------------------------------------------------------------------------- // // all the netd_*() stuff (interface TinyUSB -> driver) -- cgit v1.3.1 From a0249ada9096365697340031a7b4a285beb18a2b Mon Sep 17 00:00:00 2001 From: hathach Date: Wed, 12 Aug 2026 22:36:49 +0700 Subject: usbd: don't leak the queued-setup counter when the event queue is full A SETUP arriving while the event queue is full is silently dropped by queue_event(), but _usbd_queued_setup has already been incremented. The leaked count makes the event handler skip every subsequent SETUP ("Skipped since there is other SETUP in queue") forever: EP0 stays deaf until tud_init() while the device otherwise looks alive - enumerated, endpoints armed. Undo the increment when the enqueue fails. Unit test: fill the queue so a SETUP is dropped, then verify the next SETUP still completes a GET_DESCRIPTOR control transfer. --- src/device/usbd.c | 6 +++-- test/unit-test/test/device/usbd/test_usbd.c | 38 +++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/device/usbd.c b/src/device/usbd.c index 5471e132d..b77b766dd 100644 --- a/src/device/usbd.c +++ b/src/device/usbd.c @@ -1473,8 +1473,10 @@ TU_ATTR_FAST_FUNC void dcd_event_handler(dcd_event_t const* event, bool in_isr) break; } - if (send) { - queue_event(event, in_isr); + if (send && !queue_event(event, in_isr) && event->event_id == DCD_EVENT_SETUP_RECEIVED) { + // dropped by a full queue: undo the increment, else every later SETUP is skipped as + // "other SETUP in queue" and EP0 is deaf until re-init + _usbd_queued_setup--; } } diff --git a/test/unit-test/test/device/usbd/test_usbd.c b/test/unit-test/test/device/usbd/test_usbd.c index 7f3c3f5b2..935a20221 100644 --- a/test/unit-test/test/device/usbd/test_usbd.c +++ b/test/unit-test/test/device/usbd/test_usbd.c @@ -270,6 +270,44 @@ void test_usbd_control_in_zlp(void) tud_task(); } +//--------------------------------------------------------------------+ +// SETUP dropped by full event queue +//--------------------------------------------------------------------+ + +// When the event queue is full, queue_event() drops the SETUP event. The queued-setup +// counter must not keep the dropped SETUP's increment: a leaked count makes the handler +// skip every later SETUP ("other SETUP in queue") forever, leaving EP0 permanently deaf. +void test_usbd_setup_dropped_by_full_queue_recovers(void) +{ + // fillers drain through usbd_reset -> class reset + mscd_reset_Ignore(); + + // fill the queue to the brim, then post one more SETUP: queue_event() drops it + for (unsigned i = 0; i < CFG_TUD_TASK_QUEUE_SZ; i++) { + dcd_event_bus_signal(rhport, DCD_EVENT_UNPLUGGED, false); + } + dcd_event_setup_received(rhport, (uint8_t*) &req_get_desc_device, false); + + // drain all fillers (each tud_task pass handles at most CFG_TUD_TASK_EVENTS_PER_RUN + // events); the dropped SETUP never arrives + for (unsigned i = 0; i < (CFG_TUD_TASK_QUEUE_SZ / CFG_TUD_TASK_EVENTS_PER_RUN) + 1; i++) { + tud_task(); + } + + // the next SETUP must still be answered + desc_device = (uint8_t const*) &data_desc_device; + dcd_event_setup_received(rhport, (uint8_t*) &req_get_desc_device, false); + + dcd_edpt_xfer_ExpectWithArrayAndReturn(rhport, 0x80, (uint8_t*) &data_desc_device, sizeof(tusb_desc_device_t), sizeof(tusb_desc_device_t), false, true); + dcd_event_xfer_complete(rhport, EDPT_CTRL_IN, sizeof(tusb_desc_device_t), 0, false); + + dcd_edpt_xfer_ExpectAndReturn(rhport, EDPT_CTRL_OUT, NULL, 0, false, true); + dcd_event_xfer_complete(rhport, EDPT_CTRL_OUT, 0, 0, false); + dcd_edpt0_status_complete_ExpectWithArray(rhport, &req_get_desc_device, 1); + + tud_task(); +} + //--------------------------------------------------------------------+ // Control OUT data stage host overrun //--------------------------------------------------------------------+ -- cgit v1.3.1 From a52562b2be7ea728a176ae94d8a18d9ae0a4423a Mon Sep 17 00:00:00 2001 From: hathach Date: Wed, 12 Aug 2026 22:37:06 +0700 Subject: usbd: clear the queued-setup counter on bus reset A SETUP counted before a bus reset must not be carried across it: the consumer would either skip a post-reset SETUP (count drained by the stale entry) or, if the count leaked high for any other reason, skip them all. usbd_reset() now zeroes the counter; the consumer already guards on zero, and any pre-reset SETUP still in the queue is stale by definition and correctly discarded. --- src/device/usbd.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/device/usbd.c b/src/device/usbd.c index b77b766dd..79802e70f 100644 --- a/src/device/usbd.c +++ b/src/device/usbd.c @@ -642,6 +642,8 @@ static void configuration_reset(uint8_t rhport) { static void usbd_reset(uint8_t rhport) { configuration_reset(rhport); + // discard any pre-reset SETUP still counted: a stale count skips post-reset SETUPs + _usbd_queued_setup = 0; } bool tud_task_event_ready(void) { -- cgit v1.3.1 From 91fbbd192ca9539221d3dc096f00ce77836a5d3d Mon Sep 17 00:00:00 2001 From: hathach Date: Wed, 12 Aug 2026 23:05:59 +0700 Subject: usbd: clear endpoint busy/claimed when a completion event is dropped An XFER_COMPLETE dropped by a full event queue leaves its endpoint's BUSY|CLAIMED state set forever - the consumer that normally clears it never sees the event, so usbd_edpt_claim()/usbd_edpt_xfer() fail from then on and the class never re-arms the endpoint. Clear both flags when the enqueue fails: the completion is lost either way, but the endpoint stays usable. Unit test: arm a bulk endpoint, drop its completion against a full queue, verify the endpoint can be claimed and re-armed. --- src/device/usbd.c | 16 +++++++--- test/unit-test/test/device/usbd/test_usbd.c | 47 +++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/device/usbd.c b/src/device/usbd.c index 79802e70f..f5c3046d6 100644 --- a/src/device/usbd.c +++ b/src/device/usbd.c @@ -1475,10 +1475,18 @@ TU_ATTR_FAST_FUNC void dcd_event_handler(dcd_event_t const* event, bool in_isr) break; } - if (send && !queue_event(event, in_isr) && event->event_id == DCD_EVENT_SETUP_RECEIVED) { - // dropped by a full queue: undo the increment, else every later SETUP is skipped as - // "other SETUP in queue" and EP0 is deaf until re-init - _usbd_queued_setup--; + if (send && !queue_event(event, in_isr)) { + // event dropped by a full queue: undo state that would otherwise wedge permanently + if (event->event_id == DCD_EVENT_SETUP_RECEIVED) { + // undo the increment, else every later SETUP is skipped as "other SETUP in queue" + // and EP0 is deaf until re-init + _usbd_queued_setup--; + } else if (event->event_id == DCD_EVENT_XFER_COMPLETE) { + // clear busy + claimed, else the endpoint can never be claimed or re-armed again + uint8_t const epnum = tu_edpt_number(event->xfer_complete.ep_addr); + uint8_t const ep_dir = tu_edpt_dir(event->xfer_complete.ep_addr); + _usbd_dev.ep_status[epnum][ep_dir] &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED); + } } } diff --git a/test/unit-test/test/device/usbd/test_usbd.c b/test/unit-test/test/device/usbd/test_usbd.c index 935a20221..849097326 100644 --- a/test/unit-test/test/device/usbd/test_usbd.c +++ b/test/unit-test/test/device/usbd/test_usbd.c @@ -29,6 +29,7 @@ #include "tusb_fifo.h" #include "tusb.h" #include "usbd.h" +#include "device/usbd_pvt.h" TEST_SOURCE_FILE("usbd.c") // Mock File @@ -308,6 +309,52 @@ void test_usbd_setup_dropped_by_full_queue_recovers(void) tud_task(); } +//--------------------------------------------------------------------+ +// Transfer completion dropped by full event queue +//--------------------------------------------------------------------+ + +// When the event queue is full, queue_event() drops the XFER_COMPLETE event. The endpoint's +// busy/claimed state must not survive the dropped completion: a leaked BUSY makes every later +// usbd_edpt_claim()/usbd_edpt_xfer() on that endpoint fail, so the class never re-arms it. +void test_usbd_xfer_complete_dropped_by_full_queue_recovers(void) +{ + // fillers drain through usbd_reset -> class reset + mscd_reset_Ignore(); + + // open + claim + arm a bulk OUT endpoint the way a class driver would + tusb_desc_endpoint_t desc_ep = { + .bLength = sizeof(tusb_desc_endpoint_t), + .bDescriptorType = TUSB_DESC_ENDPOINT, + .bEndpointAddress = 0x01, + .bmAttributes = { .xfer = TUSB_XFER_BULK }, + .wMaxPacketSize = 64, + .bInterval = 0 + }; + static uint8_t xfer_buf[64]; + + dcd_edpt_open_ExpectAndReturn(rhport, &desc_ep, true); + TEST_ASSERT_TRUE(usbd_edpt_open(rhport, &desc_ep)); + TEST_ASSERT_TRUE(usbd_edpt_claim(rhport, 0x01)); + dcd_edpt_xfer_ExpectAndReturn(rhport, 0x01, xfer_buf, 64, false, true); + TEST_ASSERT_TRUE(usbd_edpt_xfer(rhport, 0x01, xfer_buf, 64, false)); + + // fill the queue to the brim, then complete the transfer: queue_event() drops it + for (unsigned i = 0; i < CFG_TUD_TASK_QUEUE_SZ; i++) { + dcd_event_bus_signal(rhport, DCD_EVENT_UNPLUGGED, false); + } + dcd_event_xfer_complete(rhport, 0x01, 64, XFER_RESULT_SUCCESS, false); + + // the endpoint must be re-armable: the dropped completion must not leak busy/claimed + TEST_ASSERT_TRUE(usbd_edpt_claim(rhport, 0x01)); + dcd_edpt_xfer_ExpectAndReturn(rhport, 0x01, xfer_buf, 64, false, true); + TEST_ASSERT_TRUE(usbd_edpt_xfer(rhport, 0x01, xfer_buf, 64, false)); + + // drain the fillers so later tests start from an empty queue + for (unsigned i = 0; i < (CFG_TUD_TASK_QUEUE_SZ / CFG_TUD_TASK_EVENTS_PER_RUN) + 1; i++) { + tud_task(); + } +} + //--------------------------------------------------------------------+ // Control OUT data stage host overrun //--------------------------------------------------------------------+ -- cgit v1.3.1 From 282d46e68d9100af0dfdcc01e7689bb63bbf8419 Mon Sep 17 00:00:00 2001 From: ice458 <85405449+ice458@users.noreply.github.com> Date: Fri, 14 Aug 2026 16:00:06 +0900 Subject: usbtmc: re-arm (or stall) the bulk-OUT endpoint after a USB488 TRIGGER A single USB488 TRIGGER message left the bulk-OUT endpoint un-armed, so the host's next bulk-OUT transfer timed out. The trigger itself succeeded silently, so the failure surfaced on a later, unrelated command; only a USBTMC device clear recovered it. The bundled examples/device/usbtmc reproduced this as shipped. Every other branch of the STATE_IDLE dispatch in usbtmcd_xfer_cb() leaves the endpoint in a defined state: it either transitions out of STATE_IDLE so a later tud_usbtmc_start_bus_read() can re-arm it, or it stalls and lets the CLEAR_FEATURE(ENDPOINT_HALT) handler recover it. USBTMC_MSGID_USB488_TRIGGER did neither, and because the state stayed STATE_IDLE, even an application following the contract documented in usbtmc_device.h got a silent no-op from tud_usbtmc_start_bus_read(). Transition to STATE_NAK so the re-arm can take effect, and stall the endpoint when trigger is unsupported or the application callback rejects it, matching the existing handling for messages the driver cannot process. The callback result is deliberately not wrapped in TU_VERIFY(), which would return before the stall/re-arm and reintroduce the same hang. Since the driver now re-arms after a trigger, drop tud_usbtmc_msg_trigger_cb from the list of callbacks after which the application must do so. Fixes #3821 Co-Authored-By: Claude Opus 5 --- src/class/usbtmc/usbtmc_device.c | 18 +++++++++++++++--- src/class/usbtmc/usbtmc_device.h | 1 - 2 files changed, 15 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/class/usbtmc/usbtmc_device.c b/src/class/usbtmc/usbtmc_device.c index 07190d89f..e248341ac 100644 --- a/src/class/usbtmc/usbtmc_device.c +++ b/src/class/usbtmc/usbtmc_device.c @@ -497,9 +497,21 @@ bool usbtmcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint #if (CFG_TUD_USBTMC_ENABLE_488) case USBTMC_MSGID_USB488_TRIGGER: - // Spec says we halt the EP if we didn't declare we support it. - TU_VERIFY(usbtmc_state.capabilities->bmIntfcCapabilities488.supportsTrigger); - TU_VERIFY(tud_usbtmc_msg_trigger_cb(msg)); + // Unlike the messages above, TRIGGER is complete on arrival and has no response, so nothing else + // will move us out of STATE_IDLE. Do it here, otherwise the tud_usbtmc_start_bus_read() below (and + // any call the application makes from its callback) is a no-op and the bulk-OUT endpoint is left + // un-armed, silently timing out every subsequent host transfer. + TU_VERIFY(atomicChangeState(STATE_IDLE, STATE_NAK)); + + // Spec says we halt the EP if we didn't declare we support it; do the same when the application + // rejects the trigger. The callback result must not be wrapped in TU_VERIFY() here: returning + // early would skip both the stall and the re-arm below. + if (!usbtmc_state.capabilities->bmIntfcCapabilities488.supportsTrigger || + !tud_usbtmc_msg_trigger_cb(msg)) { + usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); + return false; + } + tud_usbtmc_start_bus_read(); break; #endif diff --git a/src/class/usbtmc/usbtmc_device.h b/src/class/usbtmc/usbtmc_device.h index 3dc700876..efda84f16 100644 --- a/src/class/usbtmc/usbtmc_device.h +++ b/src/class/usbtmc/usbtmc_device.h @@ -25,7 +25,6 @@ // * tud_usbtmc_open_cb // * tud_usbtmc_msg_data_cb // * tud_usbtmc_msgBulkIn_complete_cb -// * tud_usbtmc_msg_trigger_cb // * (successful) tud_usbtmc_check_abort_bulk_out_cb // * (successful) tud_usbtmc_check_abort_bulk_in_cb // * (successful) tud_usmtmc_bulkOut_clearFeature_cb -- cgit v1.3.1 From af81f9ef42254301c2239eed657b0adce8b466b0 Mon Sep 17 00:00:00 2001 From: ice458 <85405449+ice458@users.noreply.github.com> Date: Fri, 14 Aug 2026 16:23:38 +0900 Subject: usbtmc: document why the trigger re-arm result is ignored A false return from tud_usbtmc_start_bus_read() here does not mean arming failed: it means the endpoint is already armed, either because the application re-armed it from its trigger callback or because a transfer is still queued (usbd_edpt_xfer() reports failure when the endpoint is busy). Both cases end in STATE_IDLE, so the state cannot disambiguate them either, and stalling on the result would halt a healthy endpoint. Co-Authored-By: Claude Opus 5 --- src/class/usbtmc/usbtmc_device.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/class/usbtmc/usbtmc_device.c b/src/class/usbtmc/usbtmc_device.c index e248341ac..0e9978a81 100644 --- a/src/class/usbtmc/usbtmc_device.c +++ b/src/class/usbtmc/usbtmc_device.c @@ -511,6 +511,9 @@ bool usbtmcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); return false; } + // Result deliberately ignored: false here means the endpoint is already armed - either the + // application re-armed it from its callback, or a transfer is still queued - not that arming + // failed. Stalling on it would halt a healthy endpoint. tud_usbtmc_start_bus_read(); break; -- cgit v1.3.1 From 16629759cd26973cd8e26bee632b339e718f83ba Mon Sep 17 00:00:00 2001 From: Saulo Veríssimo Date: Fri, 14 Aug 2026 15:21:18 -0300 Subject: feat(midi2): complete the UMP stream discovery responder Adds the Device Identity Notification with an app callback, MIDI-CI version and SysEx8 stream count in FB Info, honors the Endpoint Discovery filter bitmap, and paces discovery replies by TX FIFO room. --- src/class/midi/midi2_device.c | 129 +++++++++++++++++++++++++++++++++++++----- src/class/midi/midi2_device.h | 28 +++++++++ 2 files changed, 143 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/class/midi/midi2_device.c b/src/class/midi/midi2_device.c index 1d40a2efa..e03717992 100644 --- a/src/class/midi/midi2_device.c +++ b/src/class/midi/midi2_device.c @@ -36,6 +36,9 @@ TU_ATTR_WEAK const char* tud_midi2_fb_name_cb(uint8_t itf, uint8_t fb_idx) { TU_ATTR_WEAK tud_midi2_stream_result_t tud_midi2_stream_msg_cb(uint8_t itf, const uint32_t* ump_words) { (void) itf; (void) ump_words; return MIDI2_STREAM_PASS; } +TU_ATTR_WEAK bool tud_midi2_device_identity_cb(uint8_t itf, tud_midi2_device_identity_t* identity) { + (void) itf; (void) identity; return false; +} //--------------------------------------------------------------------+ // Byte order note @@ -59,6 +62,7 @@ enum { enum { STREAM_ENDPOINT_DISCOVERY = 0x000, STREAM_ENDPOINT_INFO = 0x001, + STREAM_DEVICE_IDENTITY = 0x002, STREAM_EP_NAME = 0x003, STREAM_PROD_INSTANCE_ID = 0x004, STREAM_CONFIG_REQUEST = 0x005, @@ -103,6 +107,12 @@ typedef struct { uint8_t protocol; bool negotiated; + // Discovery reply bits waiting for TX FIFO room, drained on TX complete + uint8_t nego_pending_ep_filter; + uint8_t nego_pending_fb_filter; + uint8_t nego_pending_fb_num; // block requested by the pending discovery, 0xFF = all + uint8_t nego_pending_fb_next; // next block index to reply for + /*------------- From this point, data is not cleared by bus reset -------------*/ struct { midi2d_tx_t tx; @@ -380,6 +390,33 @@ static void _nego_send_config_notify(midi2d_interface_t* p_midi, uint8_t protoco _nego_send_ump(p_midi, msg, 4); } +static void _nego_send_device_identity(midi2d_interface_t* p_midi) { + tud_midi2_device_identity_t id; + tu_memclr(&id, sizeof(id)); + if (!tud_midi2_device_identity_cb(_itf_idx(p_midi), &id)) return; + + // Every field is a run of bytes, each carrying 7 bits, laid out in the same + // order as the MIDI 1.0 Device Inquiry reply this message mirrors. A 1-byte + // manufacturer ID occupies the first of the three bytes, the other two stay + // zero, so the caller passes it as 0x7D0000 and not 0x00007D. + uint32_t msg[4] = {0}; + msg[0] = ((uint32_t) MT_STREAM << 28) + | ((uint32_t) STREAM_DEVICE_IDENTITY << 16); + msg[1] = id.manufacturer & UINT32_C(0x7F7F7F); + // Family and model are 14-bit numbers sent least significant byte first, + // as in the Device Inquiry reply. Manufacturer above is a byte sequence + // rather than a number, so it keeps its own order. + msg[2] = ((uint32_t) (id.family & 0x7F) << 24) + | ((uint32_t) ((id.family >> 7) & 0x7F) << 16) + | ((uint32_t) (id.model & 0x7F) << 8) + | ((uint32_t) ((id.model >> 7) & 0x7F)); + msg[3] = ((uint32_t) ((id.sw_revision >> 24) & 0x7F) << 24) + | ((uint32_t) ((id.sw_revision >> 16) & 0x7F) << 16) + | ((uint32_t) ((id.sw_revision >> 8) & 0x7F) << 8) + | ((uint32_t) (id.sw_revision & 0x7F)); + _nego_send_ump(p_midi, msg, 4); +} + static void _nego_send_fb_info(midi2d_interface_t* p_midi, uint8_t fb_idx) { // Derive direction and group span for this block from the GTB descriptor. uint16_t gtb_len = 0; @@ -395,10 +432,75 @@ static void _nego_send_fb_info(midi2d_interface_t* p_midi, uint8_t fb_idx) { | ((uint32_t) fb_idx << 8) | _fb_dir_byte(type); // UI hint + bDirection from the GTB block type msg[1] = ((uint32_t) first_group << 24) - | ((uint32_t) num_groups << 16); + | ((uint32_t) num_groups << 16) + | ((uint32_t) (CFG_TUD_MIDI2_FB_CI_VERSION & 0xFF) << 8) + | ((uint32_t) (CFG_TUD_MIDI2_FB_SYSEX8_STREAMS & 0xFF)); _nego_send_ump(p_midi, msg, 4); } +// Byte cost of one stream text reply (name or product id), all packets included. +static uint16_t _nego_stream_text_bytes(bool has_index, const char* str) { + if (!str || str[0] == '\0') return 0; + const uint8_t per_pkt = has_index ? 13 : 14; + const uint16_t len = (uint16_t) strlen(str); + return (uint16_t)(((len + per_pkt - 1) / per_pkt) * 16); +} + +// Send pending discovery replies, one whole reply at a time and only when the +// TX FIFO can take it. A full-filter Endpoint Discovery asks for more bytes +// than the default FIFO holds; replies that do not fit stay pending and are +// retried from the TX complete path, paced by the transfer flow. +static void _nego_send_pending(midi2d_interface_t* p_midi) { + tu_fifo_t* tx_ff = &p_midi->ep_stream.tx.ff; + const uint16_t depth = tu_fifo_depth(tx_ff); + const uint8_t itf = _itf_idx(p_midi); + + while (p_midi->nego_pending_ep_filter) { + const uint8_t bit = (uint8_t)(p_midi->nego_pending_ep_filter & (uint8_t)(-p_midi->nego_pending_ep_filter)); + uint16_t needed; + switch (bit) { + case 0x04: needed = _nego_stream_text_bytes(false, tud_midi2_ep_name_cb(itf)); break; + case 0x08: needed = _nego_stream_text_bytes(false, tud_midi2_product_id_cb(itf)); break; + default: needed = 16; break; // endpoint info, device identity, config notify + } + if (needed > depth) needed = depth; // oversized reply: send best effort, never stall + if (tu_fifo_remaining(tx_ff) < needed) return; + + switch (bit) { + case 0x01: _nego_send_endpoint_info(p_midi); break; + case 0x02: _nego_send_device_identity(p_midi); break; + case 0x04: _nego_send_stream_text(p_midi, STREAM_EP_NAME, false, 0, tud_midi2_ep_name_cb(itf)); break; + case 0x08: _nego_send_stream_text(p_midi, STREAM_PROD_INSTANCE_ID, false, 0, tud_midi2_product_id_cb(itf)); break; + case 0x10: _nego_send_config_notify(p_midi, p_midi->protocol); break; + default: break; + } + p_midi->nego_pending_ep_filter &= (uint8_t) ~bit; + } + + const uint8_t fb_count = _gtb_block_count(p_midi); + while (p_midi->nego_pending_fb_filter && p_midi->nego_pending_fb_next < fb_count) { + const uint8_t f = p_midi->nego_pending_fb_next; + if (p_midi->nego_pending_fb_num != 0xFF && p_midi->nego_pending_fb_num != f) { + p_midi->nego_pending_fb_next++; + continue; + } + // Info and name for one block go out together to keep per-block ordering. + uint16_t needed = (p_midi->nego_pending_fb_filter & 0x01) ? 16 : 0; + if (p_midi->nego_pending_fb_filter & 0x02) { + needed = (uint16_t)(needed + _nego_stream_text_bytes(true, tud_midi2_fb_name_cb(itf, f))); + } + if (needed > depth) needed = depth; + if (tu_fifo_remaining(tx_ff) < needed) return; + + if (p_midi->nego_pending_fb_filter & 0x01) _nego_send_fb_info(p_midi, f); + if (p_midi->nego_pending_fb_filter & 0x02) { + _nego_send_stream_text(p_midi, STREAM_FB_NAME, true, f, tud_midi2_fb_name_cb(itf, f)); + } + p_midi->nego_pending_fb_next++; + } + if (p_midi->nego_pending_fb_next >= fb_count) p_midi->nego_pending_fb_filter = 0; +} + static void _nego_handle_stream_msg(midi2d_interface_t* p_midi, const uint32_t* words) { // Let the application override this message before the built-in responder. switch (tud_midi2_stream_msg_cb(_itf_idx(p_midi), words)) { @@ -421,9 +523,9 @@ static void _nego_handle_stream_msg(midi2d_interface_t* p_midi, const uint32_t* switch (status) { case STREAM_ENDPOINT_DISCOVERY: - _nego_send_endpoint_info(p_midi); - _nego_send_stream_text(p_midi, STREAM_EP_NAME, false, 0, tud_midi2_ep_name_cb(_itf_idx(p_midi))); - _nego_send_stream_text(p_midi, STREAM_PROD_INSTANCE_ID, false, 0, tud_midi2_product_id_cb(_itf_idx(p_midi))); + // Filter bitmap: each bit set asks for one individual reply. + p_midi->nego_pending_ep_filter |= (uint8_t)(words[1] & 0x1F); + _nego_send_pending(p_midi); break; case STREAM_CONFIG_REQUEST: { @@ -436,17 +538,12 @@ static void _nego_handle_stream_msg(midi2d_interface_t* p_midi, const uint32_t* break; } - case STREAM_FB_DISCOVERY: { - uint8_t fb_idx = (words[0] >> 8) & 0xFF; - uint8_t filter = words[0] & 0xFF; // bit 0: FB Info, bit 1: FB Name - uint8_t fb_count = _gtb_block_count(p_midi); - for (uint8_t f = 0; f < fb_count; f++) { - if (fb_idx != 0xFF && fb_idx != f) continue; - if (filter & 0x01) _nego_send_fb_info(p_midi, f); - if (filter & 0x02) _nego_send_stream_text(p_midi, STREAM_FB_NAME, true, f, tud_midi2_fb_name_cb(_itf_idx(p_midi), f)); - } + case STREAM_FB_DISCOVERY: + p_midi->nego_pending_fb_num = (uint8_t)((words[0] >> 8) & 0xFF); + p_midi->nego_pending_fb_filter = (uint8_t)(words[0] & 0x03); // bit 0: FB Info, bit 1: FB Name + p_midi->nego_pending_fb_next = 0; + _nego_send_pending(p_midi); break; - } default: break; @@ -824,6 +921,10 @@ bool midi2d_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint3 } tu_edpt_stream_read_xfer(ep_rx); } else if (ep_addr == ep_tx->ep_addr && result == XFER_RESULT_SUCCESS) { + // Completed transfer freed FIFO room: flush discovery replies still pending. + if (p_midi->alt_setting == 1) { + _nego_send_pending(p_midi); + } uint16_t queued = _tx_start_xfer(p_midi); // Send ZLP if no more data is queued but the last transfer was exactly mps if (queued == 0 && tu_fifo_count(&ep_tx->ff) == 0 && xferred_bytes > 0 && diff --git a/src/class/midi/midi2_device.h b/src/class/midi/midi2_device.h index 171b404b7..e3eb084d9 100644 --- a/src/class/midi/midi2_device.h +++ b/src/class/midi/midi2_device.h @@ -58,6 +58,17 @@ extern "C" { #define CFG_TUD_MIDI2_PRODUCT_ID "TinyUSB-MIDI2" #endif +// Function Block capabilities reported in Function Block Info Notification. +// The GTB descriptor carries direction and group span, but not these: they +// depend on what the application implements, so they default to "none". +#ifndef CFG_TUD_MIDI2_FB_CI_VERSION + #define CFG_TUD_MIDI2_FB_CI_VERSION 0 // 0: none or unknown, 1 or higher: MIDI-CI version +#endif + +#ifndef CFG_TUD_MIDI2_FB_SYSEX8_STREAMS + #define CFG_TUD_MIDI2_FB_SYSEX8_STREAMS 0 // 0: unsupported, 1: single, 2-255: simultaneous streams +#endif + // String descriptor index for the Group Terminal Block (iBlockItem, Table 5-6). // 0 = no string descriptor (default, spec-allowed). #ifndef CFG_TUD_MIDI2_BLOCK_STRIDX @@ -118,6 +129,17 @@ typedef enum { MIDI2_STREAM_NEGOTIATED_MIDI2, } tud_midi2_stream_result_t; +// Device identity fields, as defined for the MIDI 1.0 Device Inquiry reply and +// reused by the Device Identity Notification. Every byte carries 7 bits. +// A 1-byte System Exclusive ID goes in the first of the three manufacturer +// bytes, so 0x7D is passed as 0x7D0000. +typedef struct { + uint32_t manufacturer; // 3 bytes, first byte is most significant + uint16_t family; // 2 bytes + uint16_t model; // 2 bytes + uint32_t sw_revision; // 4 bytes +} tud_midi2_device_identity_t; + //--------------------------------------------------------------------+ // Application Callback API (weak, optional) //--------------------------------------------------------------------+ @@ -138,6 +160,12 @@ const uint8_t* tud_midi2_gtb_desc_cb(uint8_t itf, uint16_t* len); // discovery. Return NULL or "" for no name. const char* tud_midi2_fb_name_cb(uint8_t itf, uint8_t fb_idx); +// Optional device identity, sent as a Device Identity Notification when the +// host sets the 'd' bit in the Endpoint Discovery filter. Same four fields as +// the MIDI 1.0 Device Inquiry reply. Return false to skip the notification, +// which is the default. All values are 7-bit per byte. +bool tud_midi2_device_identity_cb(uint8_t itf, tud_midi2_device_identity_t* identity); + // Optional: intercept an incoming UMP Stream message (MT 0xF). Return PASS to // let the built-in responder handle it, or HANDLED / NEGOTIATED_* if the app // answered it (e.g. via tud_midi2_n_ump_write). Lets an app override a single -- cgit v1.3.1 From 0504faf29825deb130bfeb88dba46bb1c1bdec75 Mon Sep 17 00:00:00 2001 From: Saulo Veríssimo Date: Fri, 14 Aug 2026 16:43:36 -0300 Subject: fix(midi2): keep discovery replies valid under TX pressure Text replies resume instead of dropping their tail packets, which used to leave a Start/Continue sequence without an End. A new Function Block Discovery now merges with a pending one instead of replacing it. --- src/class/midi/midi2_device.c | 90 ++++++++++++++++++++++++------------------- 1 file changed, 50 insertions(+), 40 deletions(-) (limited to 'src') diff --git a/src/class/midi/midi2_device.c b/src/class/midi/midi2_device.c index e03717992..369d380c5 100644 --- a/src/class/midi/midi2_device.c +++ b/src/class/midi/midi2_device.c @@ -112,6 +112,7 @@ typedef struct { uint8_t nego_pending_fb_filter; uint8_t nego_pending_fb_num; // block requested by the pending discovery, 0xFF = all uint8_t nego_pending_fb_next; // next block index to reply for + uint16_t nego_text_offset; // progress into the text reply being sent /*------------- From this point, data is not cleared by bus reset -------------*/ struct { @@ -337,16 +338,20 @@ static void _nego_send_endpoint_info(midi2d_interface_t* p_midi) { // index byte (the Function Block number for FB Name) and 13 chars fit per // packet; otherwise the text starts there and 14 chars fit (Endpoint Name, // Product Instance Id). -static void _nego_send_stream_text(midi2d_interface_t* p_midi, uint16_t status, - bool has_index, uint8_t index, const char* str) { - if (!str || str[0] == '\0') return; +// Sends a stream text from `offset` and returns how far it got. Resuming keeps +// the End packet, which dropping the tail would lose. +static uint16_t _nego_send_stream_text(midi2d_interface_t* p_midi, uint16_t status, + bool has_index, uint8_t index, const char* str, + uint16_t offset) { + if (!str || str[0] == '\0') return 0; - uint16_t total_len = (uint16_t) strlen(str); - uint16_t offset = 0; + const uint16_t total_len = (uint16_t) strlen(str); const uint8_t per_pkt = has_index ? 13 : 14; const uint8_t head_chars = has_index ? 1 : 2; // chars carried in word0 + if (offset >= total_len) return total_len; while (offset < total_len) { + if (tu_fifo_remaining(&p_midi->ep_stream.tx.ff) < 16) break; uint16_t remaining = total_len - offset; uint8_t n = (uint8_t)((remaining > per_pkt) ? per_pkt : remaining); bool is_first = (offset == 0); @@ -380,6 +385,7 @@ static void _nego_send_stream_text(midi2d_interface_t* p_midi, uint16_t status, _nego_send_ump(p_midi, msg, 4); offset += n; } + return offset; } static void _nego_send_config_notify(midi2d_interface_t* p_midi, uint8_t protocol) { @@ -438,41 +444,37 @@ static void _nego_send_fb_info(midi2d_interface_t* p_midi, uint8_t fb_idx) { _nego_send_ump(p_midi, msg, 4); } -// Byte cost of one stream text reply (name or product id), all packets included. -static uint16_t _nego_stream_text_bytes(bool has_index, const char* str) { - if (!str || str[0] == '\0') return 0; - const uint8_t per_pkt = has_index ? 13 : 14; - const uint16_t len = (uint16_t) strlen(str); - return (uint16_t)(((len + per_pkt - 1) / per_pkt) * 16); -} - // Send pending discovery replies, one whole reply at a time and only when the // TX FIFO can take it. A full-filter Endpoint Discovery asks for more bytes // than the default FIFO holds; replies that do not fit stay pending and are // retried from the TX complete path, paced by the transfer flow. static void _nego_send_pending(midi2d_interface_t* p_midi) { tu_fifo_t* tx_ff = &p_midi->ep_stream.tx.ff; - const uint16_t depth = tu_fifo_depth(tx_ff); const uint8_t itf = _itf_idx(p_midi); while (p_midi->nego_pending_ep_filter) { const uint8_t bit = (uint8_t)(p_midi->nego_pending_ep_filter & (uint8_t)(-p_midi->nego_pending_ep_filter)); - uint16_t needed; + const char* text = NULL; + uint16_t status = 0; switch (bit) { - case 0x04: needed = _nego_stream_text_bytes(false, tud_midi2_ep_name_cb(itf)); break; - case 0x08: needed = _nego_stream_text_bytes(false, tud_midi2_product_id_cb(itf)); break; - default: needed = 16; break; // endpoint info, device identity, config notify + case 0x04: text = tud_midi2_ep_name_cb(itf); status = STREAM_EP_NAME; break; + case 0x08: text = tud_midi2_product_id_cb(itf); status = STREAM_PROD_INSTANCE_ID; break; + default: break; } - if (needed > depth) needed = depth; // oversized reply: send best effort, never stall - if (tu_fifo_remaining(tx_ff) < needed) return; - switch (bit) { - case 0x01: _nego_send_endpoint_info(p_midi); break; - case 0x02: _nego_send_device_identity(p_midi); break; - case 0x04: _nego_send_stream_text(p_midi, STREAM_EP_NAME, false, 0, tud_midi2_ep_name_cb(itf)); break; - case 0x08: _nego_send_stream_text(p_midi, STREAM_PROD_INSTANCE_ID, false, 0, tud_midi2_product_id_cb(itf)); break; - case 0x10: _nego_send_config_notify(p_midi, p_midi->protocol); break; - default: break; + if (text != NULL) { + p_midi->nego_text_offset = _nego_send_stream_text(p_midi, status, false, 0, text, + p_midi->nego_text_offset); + if (p_midi->nego_text_offset < (uint16_t) strlen(text)) return; // resume on TX complete + p_midi->nego_text_offset = 0; + } else { + if (tu_fifo_remaining(tx_ff) < 16) return; + switch (bit) { + case 0x01: _nego_send_endpoint_info(p_midi); break; + case 0x02: _nego_send_device_identity(p_midi); break; + case 0x10: _nego_send_config_notify(p_midi, p_midi->protocol); break; + default: break; + } } p_midi->nego_pending_ep_filter &= (uint8_t) ~bit; } @@ -484,17 +486,16 @@ static void _nego_send_pending(midi2d_interface_t* p_midi) { p_midi->nego_pending_fb_next++; continue; } - // Info and name for one block go out together to keep per-block ordering. - uint16_t needed = (p_midi->nego_pending_fb_filter & 0x01) ? 16 : 0; - if (p_midi->nego_pending_fb_filter & 0x02) { - needed = (uint16_t)(needed + _nego_stream_text_bytes(true, tud_midi2_fb_name_cb(itf, f))); + if ((p_midi->nego_pending_fb_filter & 0x01) && p_midi->nego_text_offset == 0) { + if (tu_fifo_remaining(tx_ff) < 16) return; + _nego_send_fb_info(p_midi, f); } - if (needed > depth) needed = depth; - if (tu_fifo_remaining(tx_ff) < needed) return; - - if (p_midi->nego_pending_fb_filter & 0x01) _nego_send_fb_info(p_midi, f); if (p_midi->nego_pending_fb_filter & 0x02) { - _nego_send_stream_text(p_midi, STREAM_FB_NAME, true, f, tud_midi2_fb_name_cb(itf, f)); + const char* name = tud_midi2_fb_name_cb(itf, f); + p_midi->nego_text_offset = _nego_send_stream_text(p_midi, STREAM_FB_NAME, true, f, name, + p_midi->nego_text_offset); + if (name != NULL && p_midi->nego_text_offset < (uint16_t) strlen(name)) return; + p_midi->nego_text_offset = 0; } p_midi->nego_pending_fb_next++; } @@ -538,12 +539,21 @@ static void _nego_handle_stream_msg(midi2d_interface_t* p_midi, const uint32_t* break; } - case STREAM_FB_DISCOVERY: - p_midi->nego_pending_fb_num = (uint8_t)((words[0] >> 8) & 0xFF); - p_midi->nego_pending_fb_filter = (uint8_t)(words[0] & 0x03); // bit 0: FB Info, bit 1: FB Name - p_midi->nego_pending_fb_next = 0; + case STREAM_FB_DISCOVERY: { + const uint8_t req_num = (uint8_t)((words[0] >> 8) & 0xFF); + // Merge with a pending request: repeating a Function Block Info is allowed + // at any time, losing a requested one is not. + if (p_midi->nego_pending_fb_filter && p_midi->nego_pending_fb_num != req_num) { + p_midi->nego_pending_fb_num = 0xFF; + p_midi->nego_pending_fb_next = 0; + } else if (!p_midi->nego_pending_fb_filter) { + p_midi->nego_pending_fb_num = req_num; + p_midi->nego_pending_fb_next = 0; + } + p_midi->nego_pending_fb_filter |= (uint8_t)(words[0] & 0x03); // bit 0: FB Info, bit 1: FB Name _nego_send_pending(p_midi); break; + } default: break; -- cgit v1.3.1 From dfd197ff0c83a01ac55a99b85f2e8f3794ea0a47 Mon Sep 17 00:00:00 2001 From: HiFiPhile Date: Sat, 15 Aug 2026 05:11:55 +0200 Subject: fix(midi2): fix discovery response racing Signed-off-by: HiFiPhile --- src/class/midi/midi2_device.c | 97 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 79 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/class/midi/midi2_device.c b/src/class/midi/midi2_device.c index 369d380c5..b0a9e2503 100644 --- a/src/class/midi/midi2_device.c +++ b/src/class/midi/midi2_device.c @@ -112,7 +112,10 @@ typedef struct { uint8_t nego_pending_fb_filter; uint8_t nego_pending_fb_num; // block requested by the pending discovery, 0xFF = all uint8_t nego_pending_fb_next; // next block index to reply for + bool nego_pending_fb_restart; // restart after the active FB name when requests merge + uint16_t nego_text_status; // text reply owning nego_text_offset, 0 = none uint16_t nego_text_offset; // progress into the text reply being sent + uint8_t nego_text_index; // Function Block index for an active FB name /*------------- From this point, data is not cleared by bus reset -------------*/ struct { @@ -444,29 +447,85 @@ static void _nego_send_fb_info(midi2d_interface_t* p_midi, uint8_t fb_idx) { _nego_send_ump(p_midi, msg, 4); } +static void _nego_clear_pending(midi2d_interface_t* p_midi) { + p_midi->nego_pending_ep_filter = 0; + p_midi->nego_pending_fb_filter = 0; + p_midi->nego_pending_fb_num = 0; + p_midi->nego_pending_fb_next = 0; + p_midi->nego_pending_fb_restart = false; + p_midi->nego_text_status = 0; + p_midi->nego_text_offset = 0; + p_midi->nego_text_index = 0; +} + +static const char* _nego_text_cb(midi2d_interface_t* p_midi, uint16_t status, uint8_t index) { + const uint8_t itf = _itf_idx(p_midi); + switch (status) { + case STREAM_EP_NAME: return tud_midi2_ep_name_cb(itf); + case STREAM_PROD_INSTANCE_ID: return tud_midi2_product_id_cb(itf); + case STREAM_FB_NAME: return tud_midi2_fb_name_cb(itf, index); + default: return NULL; + } +} + +// Send or resume one text reply. While it is incomplete, its status and index +// identify the sole owner of nego_text_offset so another discovery request +// cannot resume a different string from the same offset. +static bool _nego_send_text(midi2d_interface_t* p_midi, uint16_t status, uint8_t index) { + const char* text = _nego_text_cb(p_midi, status, index); + const uint16_t len = text ? (uint16_t) strlen(text) : 0; + + p_midi->nego_text_status = status; + p_midi->nego_text_index = index; + p_midi->nego_text_offset = _nego_send_stream_text(p_midi, status, status == STREAM_FB_NAME, + index, text, p_midi->nego_text_offset); + if (p_midi->nego_text_offset < len) return false; + + p_midi->nego_text_status = 0; + p_midi->nego_text_offset = 0; + p_midi->nego_text_index = 0; + return true; +} + // Send pending discovery replies, one whole reply at a time and only when the // TX FIFO can take it. A full-filter Endpoint Discovery asks for more bytes // than the default FIFO holds; replies that do not fit stay pending and are // retried from the TX complete path, paced by the transfer flow. static void _nego_send_pending(midi2d_interface_t* p_midi) { tu_fifo_t* tx_ff = &p_midi->ep_stream.tx.ff; - const uint8_t itf = _itf_idx(p_midi); + + // An incomplete text sequence must finish before any newly arrived request + // is serviced; otherwise its Continue/End packets could be attached to a + // different Endpoint or Function Block string. + if (p_midi->nego_text_status) { + const uint16_t status = p_midi->nego_text_status; + const uint8_t index = p_midi->nego_text_index; + if (!_nego_send_text(p_midi, status, index)) return; + + if (status == STREAM_FB_NAME) { + if (p_midi->nego_pending_fb_restart) { + p_midi->nego_pending_fb_next = 0; + p_midi->nego_pending_fb_restart = false; + } else { + p_midi->nego_pending_fb_next++; + } + } else { + const uint8_t bit = (status == STREAM_EP_NAME) ? 0x04 : 0x08; + p_midi->nego_pending_ep_filter &= (uint8_t) ~bit; + } + } while (p_midi->nego_pending_ep_filter) { const uint8_t bit = (uint8_t)(p_midi->nego_pending_ep_filter & (uint8_t)(-p_midi->nego_pending_ep_filter)); - const char* text = NULL; uint16_t status = 0; switch (bit) { - case 0x04: text = tud_midi2_ep_name_cb(itf); status = STREAM_EP_NAME; break; - case 0x08: text = tud_midi2_product_id_cb(itf); status = STREAM_PROD_INSTANCE_ID; break; + case 0x04: status = STREAM_EP_NAME; break; + case 0x08: status = STREAM_PROD_INSTANCE_ID; break; default: break; } - if (text != NULL) { - p_midi->nego_text_offset = _nego_send_stream_text(p_midi, status, false, 0, text, - p_midi->nego_text_offset); - if (p_midi->nego_text_offset < (uint16_t) strlen(text)) return; // resume on TX complete - p_midi->nego_text_offset = 0; + if (status != 0) { + if (!_nego_send_text(p_midi, status, 0)) return; } else { if (tu_fifo_remaining(tx_ff) < 16) return; switch (bit) { @@ -491,11 +550,7 @@ static void _nego_send_pending(midi2d_interface_t* p_midi) { _nego_send_fb_info(p_midi, f); } if (p_midi->nego_pending_fb_filter & 0x02) { - const char* name = tud_midi2_fb_name_cb(itf, f); - p_midi->nego_text_offset = _nego_send_stream_text(p_midi, STREAM_FB_NAME, true, f, name, - p_midi->nego_text_offset); - if (name != NULL && p_midi->nego_text_offset < (uint16_t) strlen(name)) return; - p_midi->nego_text_offset = 0; + if (!_nego_send_text(p_midi, STREAM_FB_NAME, f)) return; } p_midi->nego_pending_fb_next++; } @@ -541,16 +596,21 @@ static void _nego_handle_stream_msg(midi2d_interface_t* p_midi, const uint32_t* case STREAM_FB_DISCOVERY: { const uint8_t req_num = (uint8_t)((words[0] >> 8) & 0xFF); + const uint8_t req_filter = (uint8_t)(words[0] & 0x03); // Merge with a pending request: repeating a Function Block Info is allowed // at any time, losing a requested one is not. - if (p_midi->nego_pending_fb_filter && p_midi->nego_pending_fb_num != req_num) { - p_midi->nego_pending_fb_num = 0xFF; - p_midi->nego_pending_fb_next = 0; + if (req_filter && p_midi->nego_pending_fb_filter) { + if (p_midi->nego_pending_fb_num != req_num) p_midi->nego_pending_fb_num = 0xFF; + if (p_midi->nego_text_status == STREAM_FB_NAME) { + p_midi->nego_pending_fb_restart = true; + } else { + p_midi->nego_pending_fb_next = 0; + } } else if (!p_midi->nego_pending_fb_filter) { p_midi->nego_pending_fb_num = req_num; p_midi->nego_pending_fb_next = 0; } - p_midi->nego_pending_fb_filter |= (uint8_t)(words[0] & 0x03); // bit 0: FB Info, bit 1: FB Name + p_midi->nego_pending_fb_filter |= req_filter; // bit 0: FB Info, bit 1: FB Name _nego_send_pending(p_midi); break; } @@ -861,6 +921,7 @@ bool midi2d_control_xfer_cb(uint8_t rhport, uint8_t stage, const tusb_control_re tu_edpt_stream_clear(&p_midi->ep_stream.rx); tu_fifo_clear(&p_midi->ep_stream.tx.ff); + _nego_clear_pending(p_midi); if (alt == 1) { p_midi->negotiated = false; -- cgit v1.3.1 From 8737c5adfca7e51003e743bcc8bcefed837fb1d8 Mon Sep 17 00:00:00 2001 From: Zixun LI Date: Sat, 15 Aug 2026 05:51:43 +0200 Subject: Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: HiFiPhile --- src/class/video/video_device.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/class/video/video_device.c b/src/class/video/video_device.c index 390349f13..770595178 100644 --- a/src/class/video/video_device.c +++ b/src/class/video/video_device.c @@ -1144,13 +1144,10 @@ static int handle_video_stm_cs_req(uint8_t rhport, uint8_t stage, video_probe_and_commit_control_t *param = &stm->probe_commit_payload; TU_VERIFY(_update_streaming_parameters(stm, param), VIDEO_ERROR_INVALID_VALUE_WITHIN_RANGE); /* Set the negotiated value */ - stm->max_payload_transfer_size = param->dwMaxPayloadTransferSize; - /* A host may commit before the parameters are fully negotiated, in which case - * _update_streaming_parameters returns early without capping the payload size. - * Clamp here so a bulk stream cannot overrun the endpoint buffer. */ - if (CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE < stm->max_payload_transfer_size) { - stm->max_payload_transfer_size = CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE; + if (CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE < param->dwMaxPayloadTransferSize) { + param->dwMaxPayloadTransferSize = CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE; } + stm->max_payload_transfer_size = param->dwMaxPayloadTransferSize; int ret = tud_video_commit_cb(stm->index_vc, stm->index_vs, param); if (VIDEO_ERROR_NONE == ret) { stm->state = VS_STATE_COMMITTED; -- cgit v1.3.1 From 8ccd0d549798c66d484e5a4b4c57edf49e8bb097 Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 13 Aug 2026 14:35:01 +0700 Subject: portable/chipidea: name SBUSCFG in ci_hs_regs_t, unify AHB burst hook Replace the duplicated per-MCU dispatch in dcd_init/hcd_init and the two helper flavors (USB_Type access on iMX RT, raw offset 0x90 on LPC18/43) with one SBUSCFG register field plus a per-header CI_HS_SET_AHB_BURST() hook, compiled only where defined. The LPC USB0-only policy is now visible at the macro definition. --- src/portable/chipidea/ci_hs/ci_hs_imxrt.h | 11 ++--------- src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h | 17 ++++------------- src/portable/chipidea/ci_hs/ci_hs_type.h | 9 ++++++++- src/portable/chipidea/ci_hs/dcd_ci_hs.c | 6 ++---- src/portable/chipidea/ci_hs/hcd_ci_hs.c | 6 ++---- 5 files changed, 18 insertions(+), 31 deletions(-) (limited to 'src') diff --git a/src/portable/chipidea/ci_hs/ci_hs_imxrt.h b/src/portable/chipidea/ci_hs/ci_hs_imxrt.h index 601e4d1c9..8f0d6083e 100644 --- a/src/portable/chipidea/ci_hs/ci_hs_imxrt.h +++ b/src/portable/chipidea/ci_hs/ci_hs_imxrt.h @@ -36,15 +36,8 @@ static const ci_hs_controller_t _ci_controller[] = #define CI_HS_REG(_port) ((ci_hs_regs_t*) _ci_controller[_port].reg_base) -enum { - // INCR16/8/4 followed by an unspecified-length burst for the remainder. - CI_HS_IMXRT_AHBBRST_INCR16_UNSPEC = 0x07u, -}; - -TU_ATTR_ALWAYS_INLINE static inline void ci_hs_imxrt_set_ahb_burst(uint8_t rhport) { - USB_Type *usb = (USB_Type *)_ci_controller[rhport].reg_base; - usb->SBUSCFG = USB_SBUSCFG_AHBBRST(CI_HS_IMXRT_AHBBRST_INCR16_UNSPEC); -} +// NXP recommends AHBBRST = INCR16 (remainder as unspecified-length bursts) +#define CI_HS_SET_AHB_BURST(_p) (CI_HS_REG(_p)->SBUSCFG = SBUSCFG_AHBBRST_INCR16_UNSPEC) //------------- DCD -------------// #define CI_DCD_INT_ENABLE(_p) NVIC_EnableIRQ ((IRQn_Type)_ci_controller[_p].irqnum) diff --git a/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h b/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h index dec3a34b1..c7dc7e69f 100644 --- a/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h +++ b/src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h @@ -34,18 +34,9 @@ static const ci_hs_controller_t _ci_controller[] = #define CI_HCD_INT_ENABLE(_p) NVIC_EnableIRQ ((IRQn_Type)_ci_controller[_p].irqnum) #define CI_HCD_INT_DISABLE(_p) NVIC_DisableIRQ((IRQn_Type)_ci_controller[_p].irqnum) -enum { - CI_HS_LPC18_43_SBUSCFG_OFFSET = 0x90u, - CI_HS_LPC18_43_AHBBRST_INCR16_UNSPEC = 0x07u, -}; - -TU_ATTR_ALWAYS_INLINE static inline void ci_hs_lpc18_43_set_ahb_burst(uint8_t rhport) { - // USB0 SBUSCFG is at offset 0x90. NXP recommends AHBBRST=0x7: - // INCR16 with non-multiple transfers decomposed into smaller unspecified bursts. - if (rhport == 0) { - volatile uint32_t *sbuscfg = (volatile uint32_t *)(_ci_controller[rhport].reg_base + CI_HS_LPC18_43_SBUSCFG_OFFSET); - *sbuscfg = CI_HS_LPC18_43_AHBBRST_INCR16_UNSPEC; - } -} +// USB0 (high-speed) only: NXP recommends AHBBRST = INCR16 (remainder as +// unspecified-length bursts) +#define CI_HS_SET_AHB_BURST(_p) \ + do { if ((_p) == 0) { CI_HS_REG(_p)->SBUSCFG = SBUSCFG_AHBBRST_INCR16_UNSPEC; } } while (0) #endif diff --git a/src/portable/chipidea/ci_hs/ci_hs_type.h b/src/portable/chipidea/ci_hs/ci_hs_type.h index 70817a6e3..b209c7545 100644 --- a/src/portable/chipidea/ci_hs/ci_hs_type.h +++ b/src/portable/chipidea/ci_hs/ci_hs_type.h @@ -71,11 +71,18 @@ enum { USBMODE_VBUS_POWER_SELECT = TU_BIT(5), // Need to be enabled for LPC18XX/43XX in host mode }; +// SBUSCFG +enum { + SBUSCFG_AHBBRST_INCR16_UNSPEC = 7, // INCR16 burst, remainder as unspecified-length bursts +}; + // Device Registers typedef struct { //------------- ID + HW Parameter Registers-------------// - volatile uint32_t TU_RESERVED[64]; ///< For iMX RT10xx, but not used by LPC18XX/LPC43XX + volatile uint32_t TU_RESERVED[36]; ///< ID/HW parameter registers, not used by this driver + volatile uint32_t SBUSCFG; ///< System Bus Interface Configuration (not present on every MCU) + volatile uint32_t TU_RESERVED[27]; //------------- Capability Registers-------------// volatile uint8_t CAPLENGTH; ///< Capability Registers Length diff --git a/src/portable/chipidea/ci_hs/dcd_ci_hs.c b/src/portable/chipidea/ci_hs/dcd_ci_hs.c index 62d75b4d3..8c08c6bd5 100644 --- a/src/portable/chipidea/ci_hs/dcd_ci_hs.c +++ b/src/portable/chipidea/ci_hs/dcd_ci_hs.c @@ -237,10 +237,8 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { usbmode |= USBMODE_CM_DEVICE; dcd_reg->USBMODE = usbmode; - #if CFG_TUSB_MCU == OPT_MCU_MIMXRT1XXX - ci_hs_imxrt_set_ahb_burst(rhport); - #elif TU_CHECK_MCU(OPT_MCU_LPC18XX, OPT_MCU_LPC43XX) - ci_hs_lpc18_43_set_ahb_burst(rhport); + #ifdef CI_HS_SET_AHB_BURST + CI_HS_SET_AHB_BURST(rhport); #endif #ifdef CFG_TUD_CI_HS_VBUS_CHARGE diff --git a/src/portable/chipidea/ci_hs/hcd_ci_hs.c b/src/portable/chipidea/ci_hs/hcd_ci_hs.c index 0fc8e4d70..0f24f5bb6 100644 --- a/src/portable/chipidea/ci_hs/hcd_ci_hs.c +++ b/src/portable/chipidea/ci_hs/hcd_ci_hs.c @@ -82,10 +82,8 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { hcd_reg->USBMODE = USBMODE_CM_HOST; #endif - #if CFG_TUSB_MCU == OPT_MCU_MIMXRT1XXX - ci_hs_imxrt_set_ahb_burst(rhport); - #elif TU_CHECK_MCU(OPT_MCU_LPC18XX, OPT_MCU_LPC43XX) - ci_hs_lpc18_43_set_ahb_burst(rhport); + #ifdef CI_HS_SET_AHB_BURST + CI_HS_SET_AHB_BURST(rhport); #endif #if !TUH_OPT_HIGH_SPEED -- cgit v1.3.1 From 073942589355676980ba401cb88c0eb9f065e468 Mon Sep 17 00:00:00 2001 From: hathach Date: Mon, 17 Aug 2026 01:01:37 +0700 Subject: usbd: split bus reset into start/end edge events A driver that can see reset signalling begin has no way to say so: the only event carries the negotiated speed, which does not exist until the reset ends. On ChipIdea that left the stack believing it was still configured for the whole reset window - 3 ms at minimum, tens of milliseconds in practice - while the controller had already torn its endpoints down, so a class driver writing in that window primed a disabled endpoint over a zeroed queue head. Add DCD_EVENT_BUS_RESET_START for the leading edge and rename the existing event to DCD_EVENT_BUS_RESET_END, keeping DCD_EVENT_BUS_RESET as an alias. START is optional and END stays self-sufficient, so every other driver and the unit tests are untouched. --- src/device/dcd.h | 26 +++++++++++++++++--------- src/device/usbd.c | 12 ++++++++++-- 2 files changed, 27 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/device/dcd.h b/src/device/dcd.h index f005e9620..a4006ae0c 100644 --- a/src/device/dcd.h +++ b/src/device/dcd.h @@ -20,19 +20,27 @@ // MACRO CONSTANT TYPEDEF PROTYPES //--------------------------------------------------------------------+ +// Bus reset is reported as two edges. BUS_RESET_START is optional: a controller that +// cannot tell the edges apart emits only BUS_RESET_END, which stays self-sufficient (it +// performs the full teardown with or without a preceding START). Emit START when reset +// signaling is detected - the link is unusable and the speed is not negotiated yet - so +// the stack stops using endpoints immediately instead of at the end of the reset. typedef enum { - DCD_EVENT_INVALID = 0, // 0 - DCD_EVENT_BUS_RESET, // 1 - DCD_EVENT_UNPLUGGED, // 2 - DCD_EVENT_SOF, // 3 - DCD_EVENT_SUSPEND, // 4 TODO LPM Sleep L1 support - DCD_EVENT_RESUME, // 5 - DCD_EVENT_SETUP_RECEIVED, // 6 - DCD_EVENT_XFER_COMPLETE, // 7 - USBD_EVENT_FUNC_CALL, // 8 Not an DCD event, just a convenient way to defer ISR function + DCD_EVENT_INVALID = 0, // 0 + DCD_EVENT_BUS_RESET_START, // 1 + DCD_EVENT_BUS_RESET_END, // 2 with negotiated speed + DCD_EVENT_UNPLUGGED, // 3 + DCD_EVENT_SOF, // 4 + DCD_EVENT_SUSPEND, // 5 TODO LPM Sleep L1 support + DCD_EVENT_RESUME, // 6 + DCD_EVENT_SETUP_RECEIVED, // 7 + DCD_EVENT_XFER_COMPLETE, // 8 + USBD_EVENT_FUNC_CALL, // 9 Not an DCD event, just a convenient way to defer ISR function DCD_EVENT_COUNT } dcd_eventid_t; +#define DCD_EVENT_BUS_RESET DCD_EVENT_BUS_RESET_END // backward compatibility + typedef struct TU_ATTR_ALIGNED(4) { uint8_t rhport; uint8_t event_id; diff --git a/src/device/usbd.c b/src/device/usbd.c index f5c3046d6..7215a8dc5 100644 --- a/src/device/usbd.c +++ b/src/device/usbd.c @@ -456,7 +456,8 @@ TU_ATTR_WEAK bool dcd_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg #if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL static char const *const _usbd_event_str[DCD_EVENT_COUNT] = { "Invalid", - "Bus Reset", + "Bus Reset Start", + "Bus Reset End", "Unplugged", "SOF", "Suspend", @@ -697,8 +698,15 @@ void tud_task_ext(uint32_t timeout_ms, bool in_isr) { #endif switch (event.event_id) { - case DCD_EVENT_BUS_RESET: + case DCD_EVENT_BUS_RESET_START: + TU_LOG_USBD("\r\n"); + usbd_reset(event.rhport); + break; + + case DCD_EVENT_BUS_RESET_END: TU_LOG_USBD(": %s Speed\r\n", tu_str_speed[event.bus_reset.speed]); + // TODO a DCD that reports both edges pays for two teardowns: track a per-rhport + // "start seen" flag and skip this reset, keeping it for the single-event DCDs. usbd_reset(event.rhport); _usbd_dev.speed = event.bus_reset.speed; break; -- cgit v1.3.1 From 2fda873fa5f6ef0c893f4f138b5c54e49c24e0a9 Mon Sep 17 00:00:00 2001 From: hathach Date: Mon, 17 Aug 2026 01:01:54 +0700 Subject: dcd(ci_hs): rework bus reset handling and bound the register waits A bus reset was detected only from the port change that ends it, which is late: the manual asks the DCD to clear the endpoint semaphores, cancel every prime and free the dTDs while the reset is still being driven. Enable the reset interrupt and do all of that there, in the manual's order (IMXRT1060RM 42.5.6.2.1, p.2394), including the two steps that were missing - confirming the port is still being reset, and freeing the dTDs. A failed check means the cleanup arrived late and the controller may be in an undefined state, so the manual's remedy is carried out rather than noted: a controller reset, followed by the full re-initialisation it then requires, since the reset detaches the device. The port change that ends the reset is left with what the manual gives it, the negotiated speed, which the new BUS_RESET_END event carries. A port change is classified by the interrupt that preceded it: a suspend raises no port change of its own, the resume that ends it does. Every unbounded register spin is now bounded. They waited on bits the hardware clears within a frame, but each could hang an interrupt handler outright on a controller that had stopped responding. The endpoint flush follows all three steps of IMXRT1060RM 42.5.6.6.5 (p.2413), repeating a flush the controller refuses while a packet is in progress - previously reported as success. EP0 setup handling is hardened alongside: the payload is copied out of the queue head through the volatile qualifier before ENDPTSETUPSTAT is cleared, since that clear releases the setup lockout and a back-to-back setup can overwrite the buffer immediately after, and C orders volatile accesses only against each other, so a plain memcpy may legally be sunk past the store. There is deliberately no unplug detection. IMXRT1060RM 42.7.31 (p.2470) states a zero Current Connect Status means the device "did not attach successfully or was forcibly disconnected by the software writing a zero to the Run bit ... It does not state the device being disconnected or suspended", so a cable pull raises no port change at all; VBUS via OTGSC is the manual's disconnect indicator and is board dependent. Verified on mimxrt1064_evk: 30 forced bus resets each re-enumerating at high speed with no descriptor errors, plus repeated full usbtest batteries at 30/30 across the series. --- src/portable/chipidea/ci_hs/ci_hs_type.h | 8 + src/portable/chipidea/ci_hs/dcd_ci_hs.c | 247 +++++++++++++++++++++---------- 2 files changed, 180 insertions(+), 75 deletions(-) (limited to 'src') diff --git a/src/portable/chipidea/ci_hs/ci_hs_type.h b/src/portable/chipidea/ci_hs/ci_hs_type.h index b209c7545..5baa14821 100644 --- a/src/portable/chipidea/ci_hs/ci_hs_type.h +++ b/src/portable/chipidea/ci_hs/ci_hs_type.h @@ -36,10 +36,18 @@ enum { PORTSC1_CURRENT_CONNECT_STATUS = TU_BIT(0), PORTSC1_FORCE_PORT_RESUME = TU_BIT(6), PORTSC1_SUSPEND = TU_BIT(7), + PORTSC1_PORT_RESET = TU_BIT(8), // read-only in device mode: a reset is being driven PORTSC1_FORCE_FULL_SPEED = TU_BIT(24), PORTSC1_PORT_SPEED = TU_BIT(26) | TU_BIT(27) }; +// PORTSC1 PSPD field values, once shifted down by PORTSC1_PORT_SPEED_POS. 3 is undefined. +enum { + PORTSC1_PORT_SPEED_FULL = 0, + PORTSC1_PORT_SPEED_LOW = 1, + PORTSC1_PORT_SPEED_HIGH = 2, +}; + // OTGSC enum { OTGSC_VBUS_DISCHARGE = TU_BIT(0), diff --git a/src/portable/chipidea/ci_hs/dcd_ci_hs.c b/src/portable/chipidea/ci_hs/dcd_ci_hs.c index 8c08c6bd5..6ab28e0be 100644 --- a/src/portable/chipidea/ci_hs/dcd_ci_hs.c +++ b/src/portable/chipidea/ci_hs/dcd_ci_hs.c @@ -154,6 +154,14 @@ TU_VERIFY_STATIC(sizeof(dcd_qhd_t) == 64, "size is not correct"); #define QTD_NEXT_INVALID 0x01 +// Bounded spin for register waits. The longest legitimate wait is a flush held off by a packet +// already in progress: ~50 us for a full-speed 64-byte packet, a low thousands of dependent +// register reads, so healthy hardware never approaches this bound. Exceeding it means the +// controller has stopped responding, and the spin then only serves to keep an ISR (or an +// IRQ-masked caller) from hanging outright - the 3 ms reset-cleanup window of IMXRT1060RM 42.5.6.2.1 (p.2394) +// is already unreachable in that state, and the manual's remedy there is a controller reset. +#define CI_HS_BUSY_SPIN 10000u + typedef struct { // Must be at 2K alignment // Each endpoint with direction (IN/OUT) occupies a queue head @@ -164,6 +172,17 @@ typedef struct { CFG_TUD_MEM_SECTION TU_ATTR_ALIGNED(2048) static dcd_data_t _dcd_data; +// What the next Port Change Detect will be. Each one is preceded by the interrupt that causes it: +// a reset interrupt for the end of a bus reset - where the speed first becomes final - or a +// suspend interrupt for the resume that ends the suspend. A suspend itself raises no port change, +// which is why there is no such value here. Indexed by rhport, which is 0 or 1 on every ci_hs +// variant (NOT the controller count: mcx/rw61x map rhport 1 to controller 0). +enum { + PORT_CHANGE_REASON_RESET = 0, + PORT_CHANGE_REASON_RESUME = 1, +}; +static volatile uint8_t _port_change_reason[2]; + //--------------------------------------------------------------------+ // Prototypes and Helper Functions //--------------------------------------------------------------------+ @@ -172,12 +191,37 @@ TU_ATTR_ALWAYS_INLINE static inline uint8_t ci_ep_count(const ci_hs_regs_t *dcd_ return dcd_reg->DCCPARAMS & DCCPARAMS_DEN_MASK; } +static bool controller_reset(uint8_t rhport); + //--------------------------------------------------------------------+ // Controller API //--------------------------------------------------------------------+ -/// follows LPC43xx User Manual 23.10.3 -static void bus_reset(uint8_t rhport) { +// Flush endpoint buffers, following IMXRT1060RM 42.5.6.6.5 Flushing/De-priming an Endpoint +// (p.2413): write ENDPTFLUSH, wait for the controller +// to acknowledge, then confirm ENDPTSTAT went to zero. The controller refuses the flush when a +// packet is in progress, and the manual requires the procedure be repeated until it takes. +// Callers proceed regardless of the result; the bound only prevents an ISR-context hang on dead +// hardware. +static bool flush_endpoints(ci_hs_regs_t *dcd_reg, uint32_t mask) { + uint32_t guard = CI_HS_BUSY_SPIN; + do { + dcd_reg->ENDPTFLUSH = mask; + while (dcd_reg->ENDPTFLUSH & mask) { + if (!guard--) { + return false; + } + } + } while ((dcd_reg->ENDPTSTAT & mask) && guard--); + + return !(dcd_reg->ENDPTSTAT & mask); +} + +/// Everything the manual asks of the DCD when a reset is detected, in its order: clear the setup +/// and completion semaphores, cancel every prime, check the reset is still being driven, and free +/// the dTDs. All of it belongs inside the reset window (IMXRT1060RM 42.5.6.2.1, p.2394); nothing +/// is left for the port change that ends the reset, which only reports the negotiated speed. +static void bus_reset_begin(uint8_t rhport) { ci_hs_regs_t *dcd_reg = CI_HS_REG(rhport); // The reset value for all endpoint types is the control endpoint. If one endpoint @@ -193,17 +237,24 @@ static void bus_reset(uint8_t rhport) { //------------- Clear All Registers -------------// dcd_reg->ENDPTNAK = dcd_reg->ENDPTNAK; dcd_reg->ENDPTNAKEN = 0; - dcd_reg->USBSTS = dcd_reg->USBSTS; dcd_reg->ENDPTSETUPSTAT = dcd_reg->ENDPTSETUPSTAT; dcd_reg->ENDPTCOMPLETE = dcd_reg->ENDPTCOMPLETE; - while (dcd_reg->ENDPTPRIME) {} - dcd_reg->ENDPTFLUSH = 0xFFFFFFFF; - while (dcd_reg->ENDPTFLUSH) {} - - // read reset bit in portsc + uint32_t guard = CI_HS_BUSY_SPIN; + while (dcd_reg->ENDPTPRIME && guard--) {} + dcd_reg->ENDPTFLUSH = 0xFFFFFFFFUL; + + // All of the above must land while the reset is still being driven - it lasts at least 3 ms. + // Arriving late leaves the controller in an undefined state, and the manual's remedy is to + // hardware-reset it. That clears Run/Stop, so the device detaches and the host will drive a + // fresh reset and enumeration - which is why nothing below this point is worth doing here. + if (!(dcd_reg->PORTSC1 & PORTSC1_PORT_RESET)) { + TU_LOG1("ci_hs: reset cleanup ran past the end of the reset, resetting controller\r\n"); + controller_reset(rhport); + return; // the controller detached; the host's next reset redoes everything below + } - //------------- Queue Head & Queue TD -------------// + //------------- Free all allocated dTDs: the controller will not execute them again -------------// tu_memclr(&_dcd_data, sizeof(dcd_data_t)); //------------- Set up Control Endpoints (0 OUT, 1 IN) -------------// @@ -216,21 +267,19 @@ static void bus_reset(uint8_t rhport) { dcd_dcache_clean_invalidate(&_dcd_data, sizeof(dcd_data_t)); } -bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { - (void)rh_init; - tu_memclr(&_dcd_data, sizeof(dcd_data_t)); - +/// Reset the controller and bring it back up in device mode. Also the manual's remedy when the +/// reset cleanup misses its window: the controller reset clears Run/Stop and detaches the device, +/// so it must be re-initialised completely afterwards (IMXRT1060RM 42.5.6.2.1, p.2394). +static bool controller_reset(uint8_t rhport) { ci_hs_regs_t *dcd_reg = CI_HS_REG(rhport); - TU_ASSERT(ci_ep_count(dcd_reg) <= TUP_DCD_ENDPOINT_MAX); - - #if TU_CHECK_MCU(OPT_MCU_HPM) - usb_phy_init((USB_Type *)dcd_reg, false); - #endif + tu_memclr(&_dcd_data, sizeof(dcd_data_t)); // Reset controller dcd_reg->USBCMD |= USBCMD_RESET; - while (dcd_reg->USBCMD & USBCMD_RESET) {} + uint32_t guard = CI_HS_BUSY_SPIN; + while ((dcd_reg->USBCMD & USBCMD_RESET) && guard--) {} + TU_VERIFY(!(dcd_reg->USBCMD & USBCMD_RESET)); // reached from the ISR too, so never halt here // Set mode to device, must be set immediately after reset uint32_t usbmode = dcd_reg->USBMODE & ~USBMOD_CM_MASK; @@ -257,9 +306,11 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { dcd_dcache_clean_invalidate(&_dcd_data, sizeof(dcd_data_t)); + _port_change_reason[rhport] = PORT_CHANGE_REASON_RESET; + dcd_reg->ENDPTLISTADDR = (uint32_t)_dcd_data.qhd; // Endpoint List Address has to be 2K alignment dcd_reg->USBSTS = dcd_reg->USBSTS; - dcd_reg->USBINTR = INTR_USB | INTR_ERROR | INTR_PORT_CHANGE | INTR_SUSPEND; + dcd_reg->USBINTR = INTR_USB | INTR_ERROR | INTR_PORT_CHANGE | INTR_RESET | INTR_SUSPEND; uint32_t usbcmd = dcd_reg->USBCMD; usbcmd &= ~USBCMD_INTR_THRESHOLD_MASK; // Interrupt Threshold Interval = 0 @@ -270,8 +321,22 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { return true; } +bool dcd_init(uint8_t rhport, const tusb_rhport_init_t *rh_init) { + (void)rh_init; + ci_hs_regs_t *dcd_reg = CI_HS_REG(rhport); + + TU_ASSERT(ci_ep_count(dcd_reg) <= TUP_DCD_ENDPOINT_MAX); + + #if TU_CHECK_MCU(OPT_MCU_HPM) + usb_phy_init((USB_Type *)dcd_reg, false); + #endif + + return controller_reset(rhport); +} + bool dcd_deinit(uint8_t rhport) { ci_hs_regs_t* dcd_reg = CI_HS_REG(rhport); + _port_change_reason[rhport] = PORT_CHANGE_REASON_RESET; // disable all interrupt dcd_reg->USBINTR = 0; @@ -280,9 +345,9 @@ bool dcd_deinit(uint8_t rhport) { dcd_reg->USBCMD &= ~USBCMD_RUN_STOP; // flush all endpoints - while (dcd_reg->ENDPTPRIME) {} - dcd_reg->ENDPTFLUSH = 0xFFFFFFFF; - while (dcd_reg->ENDPTFLUSH) {} + uint32_t guard = CI_HS_BUSY_SPIN; + while (dcd_reg->ENDPTPRIME && guard--) {} + flush_endpoints(dcd_reg, 0xFFFFFFFF); return true; } @@ -296,11 +361,13 @@ void dcd_int_disable(uint8_t rhport) { } void dcd_set_address(uint8_t rhport, uint8_t dev_addr) { - // Response with status first before changing device address - dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0, false); - - ci_hs_regs_t *dcd_reg = CI_HS_REG(rhport); - dcd_reg->DEVICEADDR = (dev_addr << 25) | TU_BIT(24); + // Response with status first before changing device address. A refused prime means a new + // setup superseded this transfer; staging an address whose ACK will never arrive would + // leave the device answering on it, so only arm the address when the status went out. + if (dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0, false)) { + ci_hs_regs_t *dcd_reg = CI_HS_REG(rhport); + dcd_reg->DEVICEADDR = (dev_addr << 25) | TU_BIT(24); + } } void dcd_remote_wakeup(uint8_t rhport) { @@ -468,9 +535,7 @@ bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) // dcd_dcache_clean_invalidate(&_dcd_data, sizeof(dcd_data_t)); // Flush EP - const uint32_t flush_mask = TU_BIT(epnum + (dir ? 16 : 0)); - dcd_reg->ENDPTFLUSH = flush_mask; - while (dcd_reg->ENDPTFLUSH & flush_mask) {} + flush_endpoints(dcd_reg, TU_BIT(epnum + (dir ? 16 : 0))); // disable to change max packet size ep_ctrl_clear(endptctrl, dir, ENDPTCTRL_ENABLE); @@ -496,7 +561,7 @@ void dcd_edpt_close_all(uint8_t rhport) { } } -static void qhd_start_xfer(uint8_t rhport, uint8_t epnum, uint8_t dir) { +static bool qhd_start_xfer(uint8_t rhport, uint8_t epnum, uint8_t dir) { ci_hs_regs_t *dcd_reg = CI_HS_REG(rhport); dcd_qhd_t *p_qhd = &_dcd_data.qhd[epnum][dir]; dcd_qtd_t *p_qtd = &_dcd_data.qtd[epnum][dir]; @@ -509,13 +574,22 @@ static void qhd_start_xfer(uint8_t rhport, uint8_t epnum, uint8_t dir) { dcd_dcache_clean_invalidate(&_dcd_data, sizeof(dcd_data_t)); if (epnum == 0) { - // follows UM 24.10.8.1.1 Setup packet handling using setup lockout mechanism - // wait until ENDPTSETUPSTAT before priming data/status in response TODO add time out - while (dcd_reg->ENDPTSETUPSTAT & TU_BIT(0)) {} + // Setup lockout (IMXRT1060RM 42.5.6.4.2.1 Setup Phase, p.2403): never prime EP0 while a new + // SETUP is pending. The ISR + // normally consumes ENDPTSETUPSTAT quickly; if the guard trips, fail the transfer so usbd + // releases the endpoint (a pending SETUP supersedes this response anyway; without one, usbd + // stalls EP0 and the host recovers with a fresh control transfer). + uint32_t guard = CI_HS_BUSY_SPIN; + while (dcd_reg->ENDPTSETUPSTAT & TU_BIT(0)) { + if (!guard--) { + return false; + } + } } // start transfer dcd_reg->ENDPTPRIME = TU_BIT(epnum + (dir ? 16 : 0)); + return true; } bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) { @@ -531,9 +605,7 @@ bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t to // Start qhd transfer p_qhd->ff = NULL; - qhd_start_xfer(rhport, epnum, dir); - - return true; + return qhd_start_xfer(rhport, epnum, dir); } #if !CFG_TUD_MEM_DCACHE_ENABLE @@ -584,9 +656,7 @@ bool dcd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t *ff, uint16_t // Start qhd transfer p_qhd->ff = ff; - qhd_start_xfer(rhport, epnum, dir); - - return true; + return qhd_start_xfer(rhport, epnum, dir); } #endif @@ -634,43 +704,43 @@ void dcd_int_handler(uint8_t rhport) { return; } - // Set if the port controller enters the full or high-speed operational state. - // either from Bus Reset or Suspended state - if (int_status & INTR_PORT_CHANGE) { - // TU_LOG2("PortChange %08lx\r\n", dcd_reg->PORTSC1); - - // Reset interrupt is not enabled, we manually check if Port Change is due - // to connection / disconnection - if (dcd_reg->USBSTS & INTR_RESET) { - dcd_reg->USBSTS = INTR_RESET; - - if (dcd_reg->PORTSC1 & PORTSC1_CURRENT_CONNECT_STATUS) { - const uint32_t speed = (dcd_reg->PORTSC1 & PORTSC1_PORT_SPEED) >> PORTSC1_PORT_SPEED_POS; - bus_reset(rhport); - dcd_event_bus_reset(rhport, (tusb_speed_t)speed, true); - } else { - dcd_event_bus_signal(rhport, DCD_EVENT_UNPLUGGED, true); - } - } else { - // Triggered by resuming from suspended state - if (!(dcd_reg->PORTSC1 & PORTSC1_SUSPEND)) { - dcd_event_bus_signal(rhport, DCD_EVENT_RESUME, true); - } - } - } + const uint8_t pci_reason = _port_change_reason[rhport]; // save current pci_reason if (int_status & INTR_SUSPEND) { - // TU_LOG2("Suspend %08lx\r\n", dcd_reg->PORTSC1); + _port_change_reason[rhport] = PORT_CHANGE_REASON_RESUME; // next PCI is resume + dcd_event_bus_signal(rhport, DCD_EVENT_SUSPEND, true); + } - if (dcd_reg->PORTSC1 & PORTSC1_SUSPEND) { - // Note: Host may delay more than 3 ms before and/or after bus reset before doing enumeration. - // Skip suspend event if we are not addressed - if ((dcd_reg->DEVICEADDR >> 25) & 0x0f) { - dcd_event_bus_signal(rhport, DCD_EVENT_SUSPEND, true); - } + // USB Reset Received: register cleanup runs here within the reset window (IMXRT1060RM 42.5.6.2.1, p.2394) + // and BUS_RESET_START fires now; BUS_RESET_END, with the final speed, is triggered later by PCI. + if (int_status & INTR_RESET) { + _port_change_reason[rhport] = PORT_CHANGE_REASON_RESET; + bus_reset_begin(rhport); + dcd_event_bus_signal(rhport, DCD_EVENT_BUS_RESET_START, true); + } + + // Port entered the full/high-speed operational state: the end of a bus reset, or a resume. + if (int_status & INTR_PORT_CHANGE) { + if (pci_reason == PORT_CHANGE_REASON_RESUME) { + dcd_event_bus_signal(rhport, DCD_EVENT_RESUME, true); + } else { + // the undefined encoding falls back to full speed + const uint32_t pspd = (dcd_reg->PORTSC1 & PORTSC1_PORT_SPEED) >> PORTSC1_PORT_SPEED_POS; + const tusb_speed_t speed = (pspd == PORTSC1_PORT_SPEED_LOW) ? TUSB_SPEED_LOW : + (pspd == PORTSC1_PORT_SPEED_HIGH) ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL; + dcd_event_bus_reset(rhport, speed, true); + // This reset is over, so the next port change is a resume. Leaving it at RESET instead would + // dispatch every later resume as another end-of-reset, clearing the queue heads mid-session. + _port_change_reason[rhport] = PORT_CHANGE_REASON_RESUME; } } + // No unplug detection yet, by the manual rather than by omission: IMXRT1060RM 42.7.31 (p.2470) says a zero + // Current Connect Status means the device "did not attach successfully or was forcibly + // disconnected by the software writing a zero to the Run bit ... It does not state the device + // being disconnected or suspended", so a cable pull raises no port change at all. VBUS via + // OTGSC BSV is the manual's disconnect indicator, and it is board dependent. + if (int_status & INTR_USB) { // Make sure we read the latest version of _dcd_data. dcd_dcache_clean_invalidate(&_dcd_data, sizeof(dcd_data_t)); @@ -678,7 +748,7 @@ void dcd_int_handler(uint8_t rhport) { const uint32_t edpt_complete = dcd_reg->ENDPTCOMPLETE; dcd_reg->ENDPTCOMPLETE = edpt_complete; // acknowledge - // 23.10.12.3 Failed QTD also get ENDPTCOMPLETE set + // 42.5.6.6.4 Transfer Completion (p.2413): a failed dTD also sets ENDPTCOMPLETE // nothing to do, we will submit xfer as error to usbd // if (int_status & INTR_ERROR) { } @@ -694,12 +764,39 @@ void dcd_int_handler(uint8_t rhport) { } // Set up Received - // 23.10.10.2 Operational model for setup transfers + // 42.5.6.4.2 Control Endpoint Operation Model (p.2403) // Must be after normal transfer complete since it is possible to have both previous control status + new setup // in the same frame and we should handle previous status first. if (dcd_reg->ENDPTSETUPSTAT) { + // 42.5.6.4.2.1 Setup Phase (p.2403) steps 1-2: duplicate the setup payload BEFORE clearing + // ENDPTSETUPSTAT - + // the clear releases the setup lockout and a back-to-back SETUP (usbtest case 10) can + // overwrite the queue-head buffer immediately after. The copy is read through the volatile + // qualifier rather than memcpy'd because C orders volatile accesses only against each + // other: a plain copy may legally be sunk past the lockout-releasing store below. + union { + tusb_control_request_t request; + uint8_t byte[8]; + } setup; + const volatile uint8_t *setup_src = (const volatile uint8_t *)&_dcd_data.qhd[0][0].setup_request; + for (uint8_t i = 0; i < sizeof(setup.request); i++) { + setup.byte[i] = setup_src[i]; + } dcd_reg->ENDPTSETUPSTAT = dcd_reg->ENDPTSETUPSTAT; - dcd_event_setup_received(rhport, (uint8_t *)(uintptr_t)&_dcd_data.qhd[0][0].setup_request, true); + + // Retire a status/handshake phase left primed by the previous control sequence + // (IMXRT1060RM 42.5.6.4.2.1, p.2403), which would otherwise retire the response the task is about to + // prime for this setup. Skipped when EP0 has nothing primed or priming, since the manual + // does not want the flush wait in an interrupt handler when it has nothing to do. + // One volatile read per statement: C leaves their order unspecified within a single + // expression, which IAR rejects outright (Pa082). + const uint32_t ep0_mask = TU_BIT(0) | TU_BIT(16); + const uint32_t ep0_stat = dcd_reg->ENDPTSTAT; + const uint32_t ep0_prime = dcd_reg->ENDPTPRIME; + if ((ep0_stat | ep0_prime) & ep0_mask) { + flush_endpoints(dcd_reg, ep0_mask); + } + dcd_event_setup_received(rhport, setup.byte, true); } } -- cgit v1.3.1 From a85a6afc6d98726f5edfb2d7606527c87c963dba Mon Sep 17 00:00:00 2001 From: hathach Date: Mon, 17 Aug 2026 01:02:07 +0700 Subject: usbd: handle a refused transfer without halting, and report it A refused transfer is a recoverable condition - a new setup superseding a control response, for instance - rather than a bug, but every failure path treated it as one. TU_ASSERT carries TU_BREAKPOINT, which is gated on a debugger being attached rather than on CFG_TUSB_DEBUG, so on a rig where a probe is always attached it halted the CPU even in release builds. Use TU_VERIFY on the control transfer paths, including the multi-packet data stage continuation, and drop the breakpoint from the endpoint transfer failure arm, which already marks the endpoint ready again so the next transfer can proceed. The result of usbd_control_xfer_cb() was separately dropped on the floor, leaving EP0 neither armed nor stalled and nothing recorded. It is logged now, and deliberately not stalled: a DCD refuses an EP0 prime when a newer setup is already latched, and EP0 stalls are cleared by hardware when that setup arrives, so a stall issued here would land after the auto-clear and stall the transfer that superseded this one. The pending setup re-drives EP0 by itself. --- src/device/usbd.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/device/usbd.c b/src/device/usbd.c index 7215a8dc5..e84d72fa4 100644 --- a/src/device/usbd.c +++ b/src/device/usbd.c @@ -757,7 +757,14 @@ void tud_task_ext(uint32_t timeout_ms, bool in_isr) { _usbd_dev.ep_status[epnum][ep_dir] &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED); if (0 == epnum) { - usbd_control_xfer_cb(event.rhport, ep_addr, (xfer_result_t) event.xfer_complete.result, event.xfer_complete.len); + // Not stalled on failure: a DCD refuses an EP0 prime when a newer setup is already + // latched, and EP0 stalls are cleared by hardware when that setup arrives - so a stall + // issued here lands after the auto-clear and would stall the transfer that superseded + // this one. The pending setup re-drives EP0 by itself. + if (!usbd_control_xfer_cb(event.rhport, ep_addr, (xfer_result_t) event.xfer_complete.result, + event.xfer_complete.len)) { + TU_LOG_USBD(" Control stage not continued\r\n"); + } } else { usbd_class_driver_t const* driver = get_driver(_usbd_dev.ep2drv[epnum][ep_dir]); TU_ASSERT(driver,); @@ -875,10 +882,10 @@ bool tud_control_xfer(uint8_t rhport, const tusb_control_request_t* request, voi if (ctrl_xfer->data_len > 0U) { TU_ASSERT(buffer); } - TU_ASSERT(data_stage_xact(rhport)); + TU_VERIFY(data_stage_xact(rhport)); } else { // wLength == 0: Status stage is always IN per USB 2.0 §9.3.1 - TU_ASSERT(status_stage_xact(rhport, TU_EP0_IN)); + TU_VERIFY(status_stage_xact(rhport, TU_EP0_IN)); } return true; @@ -929,7 +936,7 @@ static bool usbd_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t } if (is_ok) { - TU_ASSERT(status_stage_xact(rhport, ep_status)); + TU_VERIFY(status_stage_xact(rhport, ep_status)); } else { // Stall both IN and OUT control endpoint dcd_edpt_stall(rhport, TU_EP0_OUT); @@ -937,7 +944,7 @@ static bool usbd_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t } } else { // More data to transfer - TU_ASSERT(data_stage_xact(rhport)); + TU_VERIFY(data_stage_xact(rhport)); } return true; @@ -1608,10 +1615,12 @@ bool usbd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t t if (dcd_edpt_xfer(rhport, ep_addr, buffer, total_bytes, is_isr)) { return true; } else { - // DCD error, mark endpoint as ready to allow next transfer + // Driver refused the transfer, mark endpoint as ready to allow next transfer. This is a + // recoverable condition (e.g. a new setup superseding a control response), not a bug, so + // do not break into the debugger - TU_BREAKPOINT() halts the CPU whenever a probe is + // attached, which on a test rig is always. _usbd_dev.ep_status[epnum][dir] &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED); TU_LOG_USBD("FAILED\r\n"); - TU_BREAKPOINT(); return false; } } -- cgit v1.3.1 From 5baf5925c8b6a033de85e3b5537ea879de75e3da Mon Sep 17 00:00:00 2001 From: hathach Date: Mon, 17 Aug 2026 01:02:22 +0700 Subject: dcd(ip3511): fix DEVCMDSTAT write-1-to-clear handling and EP0 setup races DEVCMDSTAT mixes read/write fields with write-1-to-clear latches, so a blind read-modify-write writes a pending latch back as a one and silently clears it - a setup consumed that way strands EP0. Mask the latches on every update. The setup path follows the manual's order: acknowledge the latch, then read the payload. The EP0 IN interrupt is cleared along with EP0 OUT, as the control endpoint flowchart requires - a control IN completion latched before the setup must not reach usbd after it, where it would be applied to the request the setup just started and arm its status stage early. The payload is copied a byte at a time out of a buffer now declared volatile: the controller DMAs a new setup packet into it as soon as the latch is cleared, and C orders volatile accesses only against each other, so gcc sinks a plain memcpy below the guard read that follows at -O2 and -O3 - leaving only -Os, the level CI builds, correct. --- src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c | 106 +++++++++++++++++++++------ 1 file changed, 85 insertions(+), 21 deletions(-) (limited to 'src') diff --git a/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c b/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c index d5b03e4b1..42f6750b1 100644 --- a/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c +++ b/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c @@ -87,6 +87,10 @@ enum { DEVCMDSTAT_SUSPEND_CHANGE_MASK = TU_BIT(25), DEVCMDSTAT_RESET_CHANGE_MASK = TU_BIT(26), DEVCMDSTAT_VBUS_DEBOUNCED_MASK = TU_BIT(28), + + // write-1-to-clear latches + DEVCMDSTAT_W1C_MASK = DEVCMDSTAT_SETUP_RECEIVED_MASK | DEVCMDSTAT_CONNECT_CHANGE_MASK | + DEVCMDSTAT_SUSPEND_CHANGE_MASK | DEVCMDSTAT_RESET_CHANGE_MASK, }; enum { @@ -171,7 +175,9 @@ typedef struct ep_cmd_sts_t ep[2*MAX_EP_PAIRS][2]; xfer_dma_t dma[2*MAX_EP_PAIRS]; - TU_ATTR_ALIGNED(64) uint8_t setup_packet[8]; + // volatile: the controller DMAs a new setup packet into this buffer as soon as the SETUP + // latch is cleared, so reads of it must stay ordered against the register accesses around them + TU_ATTR_ALIGNED(64) volatile uint8_t setup_packet[8]; }dcd_data_t; // EP list must be 256-byte aligned @@ -180,8 +186,12 @@ typedef struct // Use CFG_TUD_MEM_SECTION to place it accordingly. CFG_TUD_MEM_SECTION TU_ATTR_ALIGNED(256) static dcd_data_t _dcd; -// Dummy buffer to fix ZLPs overwriting the buffer (probably an USB/DMA controller bug) -// TODO find way to save memory +// Dummy buffer to fix ZLPs overwriting the buffer: Errata LPC55S6x USB.5 / LPC55S2x USB.4 - the +// HS device controller always DMA-writes OUT data in 8-byte units, so up to 7 bytes land past the +// received length. This redirects the ZLP case; the general short-OUT case is unhandled here +// (TinyUSB's own endpoint buffers are sized/aligned so the spill stays inside them, but a tight +// caller buffer can be overrun by up to 7 bytes - the SDK's documented workaround is a bounce +// buffer). TODO find way to save memory CFG_TUD_MEM_SECTION TU_ATTR_ALIGNED(64) static uint8_t dummy[8]; //--------------------------------------------------------------------+ @@ -221,7 +231,7 @@ static const dcd_controller_t _dcd_controller[] = { // INTERNAL OBJECT & FUNCTION DECLARATION //--------------------------------------------------------------------+ -TU_ATTR_ALWAYS_INLINE static inline uint16_t get_buf_offset(void const * buffer) { +TU_ATTR_ALWAYS_INLINE static inline uint16_t get_buf_offset(void const volatile * buffer) { uint32_t addr = (uint32_t) buffer; TU_ASSERT( (addr & 0x3f) == 0, 0 ); return ( (addr >> 6) & 0xFFFFUL ) ; @@ -247,6 +257,16 @@ TU_ATTR_ALWAYS_INLINE static inline bool rhport_is_highspeed(uint8_t rhport) { return _dcd_controller[rhport].is_highspeed; } + +// DEVCMDSTAT mixes RW fields with write-1-to-clear latches (SETUP + the 3 change bits): a blind +// RMW writes a pending latch back as 1 and silently clears it (a SETUP eaten this way strands +// EP0). Mask the latches on every update; pass one in set_mask only to clear it. +TU_ATTR_ALWAYS_INLINE static inline void devcmdstat_update(dcd_registers_t* dcd_reg, + uint32_t clear_mask, uint32_t set_mask) { + const uint32_t v = dcd_reg->DEVCMDSTAT & ~(DEVCMDSTAT_W1C_MASK | clear_mask); + dcd_reg->DEVCMDSTAT = v | set_mask; +} + //--------------------------------------------------------------------+ // CONTROLLER API //--------------------------------------------------------------------+ @@ -284,8 +304,10 @@ bool dcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) { dcd_reg->DATABUFSTART = tu_align((uint32_t) &_dcd, TU_BIT(22)); // 22-bit alignment dcd_reg->INTSTAT = dcd_reg->INTSTAT; // clear all pending interrupt dcd_reg->INTEN = INT_DEVICE_STATUS_MASK; - dcd_reg->DEVCMDSTAT |= DEVCMDSTAT_DEVICE_ENABLE_MASK | DEVCMDSTAT_DEVICE_CONNECT_MASK | - DEVCMDSTAT_RESET_CHANGE_MASK | DEVCMDSTAT_CONNECT_CHANGE_MASK | DEVCMDSTAT_SUSPEND_CHANGE_MASK; + // deliberately clear every latch (incl. a SETUP left by a bootloader/warm start) for a + // deterministic init state + devcmdstat_update(dcd_reg, 0, DEVCMDSTAT_DEVICE_ENABLE_MASK | DEVCMDSTAT_DEVICE_CONNECT_MASK | + DEVCMDSTAT_W1C_MASK); NVIC_ClearPendingIRQ(_dcd_controller[rhport].irqnum); @@ -309,8 +331,7 @@ void dcd_set_address(uint8_t rhport, uint8_t dev_addr) // Response with status first before changing device address dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0, false); - dcd_reg->DEVCMDSTAT &= ~DEVCMDSTAT_DEVICE_ADDR_MASK; - dcd_reg->DEVCMDSTAT |= dev_addr; + devcmdstat_update(dcd_reg, DEVCMDSTAT_DEVICE_ADDR_MASK, dev_addr); } void dcd_remote_wakeup(uint8_t rhport) @@ -321,13 +342,13 @@ void dcd_remote_wakeup(uint8_t rhport) void dcd_connect(uint8_t rhport) { dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs; - dcd_reg->DEVCMDSTAT |= DEVCMDSTAT_DEVICE_CONNECT_MASK; + devcmdstat_update(dcd_reg, 0, DEVCMDSTAT_DEVICE_CONNECT_MASK); } void dcd_disconnect(uint8_t rhport) { dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs; - dcd_reg->DEVCMDSTAT &= ~DEVCMDSTAT_DEVICE_CONNECT_MASK; + devcmdstat_update(dcd_reg, DEVCMDSTAT_DEVICE_CONNECT_MASK, 0); } void dcd_sof_enable(uint8_t rhport, bool en) @@ -380,9 +401,17 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) uint8_t const ep_id = ep_addr2id(ep_addr); + // Preserve rf_tv: for non-control endpoints it is a TYPE bit, not the toggle value (UM11126: + // T=1 + RF 1/0 = interrupt/iso). Zeroing it here turned HS periodic interrupt endpoints into + // isochronous - no handshake on OUT, dead IN (usbtest cases 25/26 on lpc55 HS port). + // TODO implement the Errata LPC546xx USB.13 work-around (same semantics in UM11126): with RF/TV preserved at 1, TR + // loads the toggle from TV, so an HS interrupt endpoint restarts on DATA1 after clear-halt and + // the host discards one packet as a retransmission. The documented workaround needs an + // interrupt-on-NAK state machine (park as generic TR=1/TV=0, wait for a NAKed token to latch + // toggle 0 via EPTOGGLE, restore the type) - deferred; one lost packet beats the fully broken + // endpoint the old rf_tv clear caused. _dcd.ep[ep_id][0].cmd_sts.stall = 0; _dcd.ep[ep_id][0].cmd_sts.toggle_reset = 1; - _dcd.ep[ep_id][0].cmd_sts.rf_tv = 0; } bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * p_endpoint_desc) @@ -432,7 +461,7 @@ void dcd_edpt_close_all (uint8_t rhport) { for (uint8_t ep_id = 0; ep_id < 2*_dcd_controller[rhport].ep_pairs; ++ep_id) { - _dcd.ep[ep_id][0].cmd_sts.active = _dcd.ep[ep_id][0].cmd_sts.active = 0; // TODO proper way is to EPSKIP then wait ep[][].active then write ep[][].disable (see table 778 in LPC55S69 Use Manual) + _dcd.ep[ep_id][0].cmd_sts.active = _dcd.ep[ep_id][1].cmd_sts.active = 0; // TODO proper way is to EPSKIP then wait ep[][].active then write ep[][].disable (see table 778 in LPC55S69 Use Manual) _dcd.ep[ep_id][0].cmd_sts.disable = _dcd.ep[ep_id][1].cmd_sts.disable = 1; } } @@ -538,7 +567,7 @@ static void bus_reset(uint8_t rhport) dcd_reg->EPSKIP = 0xFFFFFFFF; dcd_reg->INTSTAT = dcd_reg->INTSTAT; // clear all pending interrupt - dcd_reg->DEVCMDSTAT |= DEVCMDSTAT_SETUP_RECEIVED_MASK; // clear setup received interrupt + devcmdstat_update(dcd_reg, 0, DEVCMDSTAT_SETUP_RECEIVED_MASK); // clear setup received interrupt dcd_reg->INTEN = INT_DEVICE_STATUS_MASK | TU_BIT(0) | TU_BIT(1); // enable device status & control endpoints } @@ -597,18 +626,25 @@ void dcd_int_handler(uint8_t rhport) { dcd_registers_t* dcd_reg = _dcd_controller[rhport].regs; - uint32_t const cmd_stat = dcd_reg->DEVCMDSTAT; - uint32_t int_status = dcd_reg->INTSTAT; - int_status &= dcd_reg->INTEN; + int_status &= dcd_reg->INTEN; dcd_reg->INTSTAT = int_status; // Acknowledge handled interrupt if (int_status == 0) return; + // Snapshot after the INTSTAT ack: latch bits persist (RWC) so nothing is lost, while the reverse + // order could consume INTSTAT bit0 for a SETUP not yet visible in the snapshot - stranding the + // SETUP (INTSTAT is edge-latched) and feeding bit0 to process_xfer_isr as a bogus completion. + uint32_t const cmd_stat = dcd_reg->DEVCMDSTAT; + //------------- Device Status -------------// if ( int_status & INT_DEVICE_STATUS_MASK ) { - dcd_reg->DEVCMDSTAT |= DEVCMDSTAT_RESET_CHANGE_MASK | DEVCMDSTAT_CONNECT_CHANGE_MASK | DEVCMDSTAT_SUSPEND_CHANGE_MASK; + // clear only the change latches observed in the snapshot: one latched by hardware between the + // snapshot and this write would be acknowledged unseen (its DEV_INT re-latches and dispatches + // next pass instead) + devcmdstat_update(dcd_reg, 0, cmd_stat & + (DEVCMDSTAT_RESET_CHANGE_MASK | DEVCMDSTAT_CONNECT_CHANGE_MASK | DEVCMDSTAT_SUSPEND_CHANGE_MASK)); if ( cmd_stat & DEVCMDSTAT_RESET_CHANGE_MASK) // bus reset { @@ -653,15 +689,43 @@ void dcd_int_handler(uint8_t rhport) _dcd.ep[0][0].cmd_sts.active = _dcd.ep[1][0].cmd_sts.active = 0; _dcd.ep[0][0].cmd_sts.stall = _dcd.ep[1][0].cmd_sts.stall = 0; - dcd_reg->DEVCMDSTAT |= DEVCMDSTAT_SETUP_RECEIVED_MASK; + // UM flow: ack the latch FIRST, then read the payload. This IP has no setup lockout, so a + // back-to-back SETUP can overwrite _dcd.setup_packet at any time - but with the latch already + // released, any such overwrite re-latches SETUP_RECEIVED and is redelivered (worst case a + // superseded duplicate, absorbed by usbd's queued-setup counter). The reverse order can + // consume the newer SETUP's latch unseen and lose it. + devcmdstat_update(dcd_reg, 0, DEVCMDSTAT_SETUP_RECEIVED_MASK); + + // UM11126 Fig 163 (control EP0 flowchart) requires clearing the EP0IN interrupt here: a + // control IN completion latched before this SETUP must not reach usbd after it, where it + // would be applied to the new request and arm its status stage early. EP0OUT goes with it - + // bit0 is set by SETUP reception too, and left set it would replay next pass as a phantom + // completion. Neither can discard live work: the SETUP latch NAKs all EP0 traffic until the + // update above, and both EP0 Active bits were cleared a few lines up. + dcd_reg->INTSTAT = TU_BIT(0) | TU_BIT(1); + + // Copied a byte at a time rather than with memcpy: C orders volatile accesses only against + // each other, so a non-volatile copy of this buffer may be sunk below the guard read that + // follows - gcc does exactly that at -O2 and -O3, leaving only -Os correct. + uint8_t setup_copy[8]; + for (uint8_t i = 0; i < sizeof(setup_copy); i++) { + setup_copy[i] = _dcd.setup_packet[i]; + } - dcd_event_setup_received(rhport, _dcd.setup_packet, true); + // a SETUP that raced in after the acks (its bit0 consumed above) makes this copy suspect: + // its latch is visible again, so re-raise the endpoint interrupt and let the next pass + // deliver the newer payload rather than passing up bytes that may be torn between the two + if (dcd_reg->DEVCMDSTAT & DEVCMDSTAT_SETUP_RECEIVED_MASK) { + dcd_reg->INTSETSTAT = TU_BIT(0); + } else { + dcd_event_setup_received(rhport, setup_copy, true); + } // keep waiting for next setup prepare_setup_packet(rhport); - // clear bit0 - int_status = tu_bit_clear(int_status, 0); + // drop both EP0 bits: acked above, and neither belongs to the request this SETUP starts + int_status &= ~(TU_BIT(0) | TU_BIT(1)); } // Endpoint transfer complete interrupt -- cgit v1.3.1 From 19ff2ed615e4a97984aab5551ac8835ead53b9e7 Mon Sep 17 00:00:00 2001 From: hathach Date: Mon, 17 Aug 2026 01:02:54 +0700 Subject: examples: document and work around the i.MX RT and LPC55 USB errata ERR050101: while an isochronous IN endpoint is active, an IN token addressed to that same endpoint number on ANOTHER device sharing the host can silently unprime one of this device's OUT endpoints - control, bulk, interrupt or isochronous alike. NXP states it cannot be detected by software and raises no interrupt, so the endpoint simply stops answering and the transfer never completes. The workaround is a uniqueness requirement rather than a particular number: the isochronous IN endpoint must not share its number with any IN endpoint in use on the bus. One family-wide constant therefore defeats it, since two affected boards on the same hub then pick the same number and each becomes the other's aggressor. CFG_TUSB_MIMXRT1XXX_ERRATA_ERR050101 is set only for the parts whose errata list it - RT1015, RT1020, RT1024 and RT1050, where it is marked no fix scheduled, plus RT1060 and RT1064 rev A - so RT1010 and the RT11xx family keep the ordinary number and cannot collide with an affected board beside them. Several affected boards on one hub can still be given distinct numbers with -DEPNUM_ISO_IN. The guard covers every example that has an isochronous IN endpoint: audio_test, audio_4_channel_mic, uac2_headset, cdc_uac2, usbtest, video_capture and video_capture_2ch. The video examples move the endpoint only when streaming isochronously, since the bulk configuration is unaffected, and video_capture_2ch takes two numbers because it has two streams. The macro name follows CFG_TUSB_RP2_ERRATA_E2/E4/E15 already in tree, and its is fixed, and which cannot be told apart at compile time - a way to define it to 0. device_issues.rst records ERR050101 against every affected part with a link to each errata sheet, and adds the LPC55S2x USB.3 speed-detection and USB.5 isochronous IN entries, neither of which TinyUSB works around. The branch's design notes are included under docs/superpowers. Verified: 340 wedge-free runs on mimxrt1064_evk, which previously wedged within hours, and the macro resolving to endpoint 0x87 on mimxrt1064_evk against 0x83 on mimxrt1010_evk and stm32f407disco. --- docs/reference/device_issues.rst | 45 ++ .../plans/2026-08-15-ci-hs-reset-edges.md | 782 +++++++++++++++++++++ .../plans/2026-08-16-drop-ep0-prime-verify.md | 314 +++++++++ .../specs/2026-08-15-ci-hs-reset-edges-design.md | 162 +++++ .../2026-08-16-drop-ep0-prime-verify-design.md | 90 +++ .../audio_4_channel_mic/src/usb_descriptors.c | 4 + examples/device/audio_test/src/usb_descriptors.c | 4 + examples/device/cdc_uac2/src/usb_descriptors.c | 10 + examples/device/uac2_headset/src/usb_descriptors.c | 7 + examples/device/usbtest/src/usb_descriptors.c | 16 + .../device/video_capture/src/usb_descriptors.c | 4 + .../device/video_capture_2ch/src/usb_descriptors.c | 11 +- src/common/tusb_mcu.h | 19 + 13 files changed, 1466 insertions(+), 2 deletions(-) create mode 100644 docs/superpowers/plans/2026-08-15-ci-hs-reset-edges.md create mode 100644 docs/superpowers/plans/2026-08-16-drop-ep0-prime-verify.md create mode 100644 docs/superpowers/specs/2026-08-15-ci-hs-reset-edges-design.md create mode 100644 docs/superpowers/specs/2026-08-16-drop-ep0-prime-verify-design.md (limited to 'src') diff --git a/docs/reference/device_issues.rst b/docs/reference/device_issues.rst index 0850409cb..b95a3fc1e 100644 --- a/docs/reference/device_issues.rst +++ b/docs/reference/device_issues.rst @@ -20,6 +20,51 @@ Most severe issues are: - USB.5: In USB full-speed host mode, linked list on done queue is broken. - USB.15: USB high-speed device in endpoint TX data corruption +NXP i.MX RT1015/RT1020/RT1024/RT1050/RT1060/RT1064 +----------------------------------------------------- +**Severity: High** when an isochronous IN endpoint is used behind a hub + +Reference: ERR050101 "USB: Endpoint conflict issue in device mode", listed in the errata sheet of +every part above - `IMXRT1015CE`_, `IMXRT1020CE`_, `IMXRT1024CE`_, `IMXRT1050CE`_, `IMXRT1060CE`_ +and `IMXRT1064CE`_. On RT1060 and RT1064 it applies to rev A silicon only and is fixed in rev B; on +RT1015, RT1020, RT1024 and RT1050 it is marked *no fix scheduled*, so all silicon is affected. +RT1010, RT116x, RT117x and RT118x do not list it. + +.. _IMXRT1015CE: https://www.nxp.com/docs/en/errata/IMXRT1015CE.pdf +.. _IMXRT1020CE: https://www.nxp.com/docs/en/errata/IMXRT1020CE.pdf +.. _IMXRT1024CE: https://www.nxp.com/docs/en/errata/IMXRT1024CE.pdf +.. _IMXRT1050CE: https://www.nxp.com/docs/en/errata/IMXRT1050CE.pdf +.. _IMXRT1060CE: https://www.nxp.com/docs/en/errata/IMXRT1060CE.pdf +.. _IMXRT1064CE: https://www.nxp.com/docs/en/errata/IMXRT1064CE.pdf + +While an isochronous IN endpoint is active, an IN token addressed to *that same endpoint number on +another device sharing the host* can silently unprime one of this device's OUT endpoints - control, +bulk, interrupt or isochronous alike. NXP states the unpriming cannot be detected by software and +raises no interrupt, so the endpoint simply stops answering OUT tokens and the transfer never +completes. Typically seen when the device is behind a hub with other devices attached. + +Workaround: give isochronous IN endpoints a number that no other device on the same host uses for +any IN endpoint - endpoints 1-3 are used by nearly every composite device, so choose a high number +(``examples/device/usbtest`` uses endpoint 7 on this family for that reason). Devices without an +isochronous IN endpoint are unaffected. + +NXP LPC55S2x/LPC552x +--------------------------------- +**Severity: Low** (both need specific conditions) + +Reference: `LPC55S2x Errata Sheet`_ USB.3, USB.5 + +.. _LPC55S2x Errata Sheet: https://www.nxp.com/docs/en/errata/ES_LPC55S2x.pdf + +USB.3: As a high-speed device behind certain full-speed hubs, the device does not correctly detect +the host's KJ chirp sequence and can behave erratically due to wrong speed detection. The documented +workaround is to set the FORCE_FS bit in DEVCMDSTAT on bus reset when the reported link speed is +full speed. TinyUSB does not implement this workaround. + +USB.5: An isochronous IN endpoint sending a 1024-byte maximum-packet-size packet raises no endpoint +interrupt and its command/status entry is not updated. Workaround: cap the isochronous IN maximum +packet size at 1023 bytes in the descriptor. + WCH CH32F20x/CH32V20x/CH32V30x --------------------------------- **Severity: Medium** diff --git a/docs/superpowers/plans/2026-08-15-ci-hs-reset-edges.md b/docs/superpowers/plans/2026-08-15-ci-hs-reset-edges.md new file mode 100644 index 000000000..ec0834e55 --- /dev/null +++ b/docs/superpowers/plans/2026-08-15-ci-hs-reset-edges.md @@ -0,0 +1,782 @@ +# Bus-Reset Edge Events + Review Fix Wave Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Give the device stack a "bus reset started" event so ci_hs can tell usbd to stand down at the URI interrupt instead of up to 50 ms later, and clear the ten findings agreed from the max review. + +**Architecture:** `DCD_EVENT_BUS_RESET` splits into `DCD_EVENT_BUS_RESET_START` / `_END` with a compatibility alias, so every other port stays byte-identical. `dcd_ci_hs.c`'s `bus_reset()` splits along the register/software line — registers at URI (`_START`), software structures at the port-change ending the reset (`_END`) — which eliminates the window where usbd believes it is configured over zeroed queue heads. A single bounded-flush helper absorbs the five flush sites. Seven mechanical fixes follow. + +**Tech Stack:** C99, TinyUSB device stack (`src/device/`), ChipIdea HS DCD (`src/portable/chipidea/ci_hs/`), NXP IP3511 DCD (`src/portable/nxp/lpc_ip3511/`), CMake+Ninja and Make builds, J-Link flashing, `test/hil/` HIL harness. + +## Global Constraints + +- Branch `fix-ci-hs` in worktree `/home/hathach/.herdr/worktrees/tinyusb/fix-ci-hs`. Do NOT push; the user pushes. +- C99, 2-space indent, no tabs. Match each file's surrounding style (`dcd_lpc_ip3511.c` mixes styles — follow the immediate neighbourhood). +- Commit messages: imperative mood, no `Co-Authored-By:` or `Claude-Session:` trailers (repo rule: hathach is sole author). +- The repo pre-commit hook (trailing-whitespace, end-of-file-fixer, codespell, unique-PIDs, ceedling unit tests) must pass. If it rewrites a file, re-stage and retry the commit once. +- Comments: short, only the non-obvious "why". Cite manuals as `UM10503 25.10.3` / `Errata LPC546xx USB.13` style — never `ES_` prefixes. +- Never edit anything under `hw/mcu/` or `lib/` (vendor code). +- Build commands used throughout (each ~30-60 s): + `cmake --build examples/cmake-build-` for `mimxrt1064_evk`, `lpcxpresso18s37`, `lpcxpresso11u37`, `lpcxpresso55s28`. +- Design source of truth: `docs/superpowers/specs/2026-08-15-ci-hs-reset-edges-design.md`. + +## File Structure + +| File | Responsibility in this plan | +|---|---| +| `src/device/dcd.h` | Event enum + compatibility alias + contract comment | +| `src/device/usbd.c` | Handle both reset edges; log strings; stop breakpointing on DCD refusal | +| `src/portable/chipidea/ci_hs/dcd_ci_hs.c` | Flush helper; `bus_reset()` split; setup-flush wait; `dcd_set_address`; RESUME guard | +| `src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c` | Torn-setup delivery; USB.13 TODO token | +| `hw/bsp/lpc55/boards/lpcxpresso55s28/board.cmake` | Delete dead RHPORT block | +| `hw/bsp/lpc11/boards/lpcxpresso11u37/lpc11u37.ld` | Correct stale comment; relabel ASSERT | + +Tasks 1-3 are ordered (each builds on the previous); Tasks 4-6 are independent of each other. + +--- + +### Task 1: Split the bus-reset event into START/END edges + +**Files:** +- Modify: `src/device/dcd.h` (enum at lines 23-34; contract comment above it) +- Modify: `src/device/usbd.c` (`_usbd_event_str[]` at line 457; the `DCD_EVENT_BUS_RESET` case at line 700) + +**Interfaces:** +- Produces: `DCD_EVENT_BUS_RESET_START` and `DCD_EVENT_BUS_RESET_END` enum members; `#define DCD_EVENT_BUS_RESET DCD_EVENT_BUS_RESET_END`. Task 2 emits `_START` via the existing `dcd_event_bus_signal(uint8_t rhport, dcd_eventid_t eid, bool in_isr)` and `_END` via the existing `dcd_event_bus_reset(uint8_t rhport, tusb_speed_t speed, bool in_isr)`. + +- [ ] **Step 1: Replace the enum member in `src/device/dcd.h`** + +Replace: + +```c +typedef enum { + DCD_EVENT_INVALID = 0, // 0 + DCD_EVENT_BUS_RESET, // 1 + DCD_EVENT_UNPLUGGED, // 2 + DCD_EVENT_SOF, // 3 + DCD_EVENT_SUSPEND, // 4 TODO LPM Sleep L1 support + DCD_EVENT_RESUME, // 5 + DCD_EVENT_SETUP_RECEIVED, // 6 + DCD_EVENT_XFER_COMPLETE, // 7 + USBD_EVENT_FUNC_CALL, // 8 Not an DCD event, just a convenient way to defer ISR function + DCD_EVENT_COUNT +} dcd_eventid_t; +``` + +with: + +```c +// Bus reset is reported as two edges. BUS_RESET_START is optional: a controller that +// cannot tell the edges apart emits only BUS_RESET_END, which stays self-sufficient (it +// performs the full teardown with or without a preceding START). Emit START when reset +// signaling is detected - the link is unusable and the speed is not negotiated yet - so +// the stack stops using endpoints immediately instead of at the end of the reset. +typedef enum { + DCD_EVENT_INVALID = 0, // 0 + DCD_EVENT_BUS_RESET_START, // 1 + DCD_EVENT_BUS_RESET_END, // 2 with negotiated speed + DCD_EVENT_UNPLUGGED, // 3 + DCD_EVENT_SOF, // 4 + DCD_EVENT_SUSPEND, // 5 TODO LPM Sleep L1 support + DCD_EVENT_RESUME, // 6 + DCD_EVENT_SETUP_RECEIVED, // 7 + DCD_EVENT_XFER_COMPLETE, // 8 + USBD_EVENT_FUNC_CALL, // 9 Not an DCD event, just a convenient way to defer ISR function + DCD_EVENT_COUNT +} dcd_eventid_t; + +#define DCD_EVENT_BUS_RESET DCD_EVENT_BUS_RESET_END // backward compatibility +``` + +- [ ] **Step 2: Update the log-string table in `src/device/usbd.c`** + +At line 457 the table is indexed by event id and MUST stay in enum order. Replace the +`"Bus Reset",` entry (line 459) with two entries: + +```c + "Bus Reset Start", + "Bus Reset End", +``` + +- [ ] **Step 3: Handle both edges in the usbd task loop** + +Replace the case at `src/device/usbd.c:700`: + +```c + case DCD_EVENT_BUS_RESET: + TU_LOG_USBD(": %s Speed\r\n", tu_str_speed[event.bus_reset.speed]); + usbd_reset(event.rhport); + _usbd_dev.speed = event.bus_reset.speed; + break; +``` + +with: + +```c + case DCD_EVENT_BUS_RESET_START: + TU_LOG_USBD("\r\n"); + usbd_reset(event.rhport); + break; + + case DCD_EVENT_BUS_RESET_END: + TU_LOG_USBD(": %s Speed\r\n", tu_str_speed[event.bus_reset.speed]); + // TODO a DCD that reports both edges pays for two teardowns: track a per-rhport + // "start seen" flag and skip this reset, keeping it for the single-event DCDs. + usbd_reset(event.rhport); + _usbd_dev.speed = event.bus_reset.speed; + break; +``` + +- [ ] **Step 4: Verify legacy ports still build (the alias must carry them)** + +Run: + +```bash +cd examples && cmake -B cmake-build-stm32f407disco -DBOARD=stm32f407disco -G Ninja -DCMAKE_BUILD_TYPE=MinSizeRel . && cmake --build cmake-build-stm32f407disco +``` + +Expected: builds clean. This board's DCD (dwc2) still calls `dcd_event_bus_reset()`, which +now resolves to `_END` through the unchanged helper — proving the alias works. + +- [ ] **Step 5: Verify the unit tests still build and pass** + +Run: `cd test/unit-test && ceedling test:all` +Expected: all tests pass (they reference `DCD_EVENT_BUS_RESET` via the alias). + +- [ ] **Step 6: Commit** + +```bash +git add src/device/dcd.h src/device/usbd.c +git commit -m "usbd: split bus reset into start/end edge events + +A DCD that can see reset signaling begin has no way to say so: the only +event carries the negotiated speed, which does not exist until the reset +ends. On ChipIdea that leaves the stack believing it is configured for the +whole reset window (3 ms minimum, tens of ms in practice) while the +controller has already torn its endpoints down. + +Add DCD_EVENT_BUS_RESET_START for the leading edge and rename the existing +event to DCD_EVENT_BUS_RESET_END, keeping DCD_EVENT_BUS_RESET as an alias +so every other port and the unit tests are untouched. START is optional and +END stays self-sufficient, so single-event drivers keep working unchanged." +``` + +--- + +### Task 2: Split ci_hs `bus_reset()` across the two edges, behind one flush helper + +**Files:** +- Modify: `src/portable/chipidea/ci_hs/dcd_ci_hs.c` (`bus_reset()`; `dcd_deinit()`; `dcd_edpt_iso_activate()`; the `INTR_RESET` and `INTR_PORT_CHANGE` branches of `dcd_int_handler()`) + +**Interfaces:** +- Consumes: `DCD_EVENT_BUS_RESET_START` (Task 1), `dcd_event_bus_signal()`, `dcd_event_bus_reset()`. +- Produces: `static bool flush_endpoints(ci_hs_regs_t *dcd_reg, uint32_t mask)` — writes `ENDPTFLUSH = mask`, spins bounded by `CI_HS_BUSY_SPIN`, returns `true` if the bits cleared. Used by Task 3. + +- [ ] **Step 1: Add the flush helper next to `bus_reset()`** + +Insert above `bus_reset()`: + +```c +// Flush endpoint buffers and wait for the controller to acknowledge. Callers proceed +// regardless of the result; the bound only prevents an ISR-context hang on dead hardware. +static bool flush_endpoints(ci_hs_regs_t *dcd_reg, uint32_t mask) { + dcd_reg->ENDPTFLUSH = mask; + uint32_t guard = CI_HS_BUSY_SPIN; + while (dcd_reg->ENDPTFLUSH & mask) { + if (!guard--) { + return false; + } + } + return true; +} +``` + +- [ ] **Step 2: Split `bus_reset()` into begin/complete** + +Replace the whole `bus_reset()` function with these two. `bus_reset_begin()` keeps only +register work; `bus_reset_complete()` owns everything that touches `_dcd_data`: + +```c +/// Register-side reset handling, must run inside the reset window (UM10503 25.10.3) +static void bus_reset_begin(uint8_t rhport) { + ci_hs_regs_t *dcd_reg = CI_HS_REG(rhport); + + // The reset value for all endpoint types is the control endpoint. If one endpoint + // direction is enabled and the paired endpoint of opposite direction is disabled, then the + // endpoint type of the unused direction must be changed from the control type to any other + // type (e.g. bulk). Leaving an un-configured endpoint control will cause undefined behavior + // for the data PID tracking on the active endpoint. + const uint8_t ep_count = ci_ep_count(dcd_reg); + for (uint8_t i = 1; i < ep_count; i++) { + dcd_reg->ENDPTCTRL[i] = ENDPTCTRL_RESET_MASK; + } + + //------------- Clear All Registers -------------// + dcd_reg->ENDPTNAK = dcd_reg->ENDPTNAK; + dcd_reg->ENDPTNAKEN = 0; + dcd_reg->ENDPTSETUPSTAT = dcd_reg->ENDPTSETUPSTAT; + dcd_reg->ENDPTCOMPLETE = dcd_reg->ENDPTCOMPLETE; + + uint32_t guard = CI_HS_BUSY_SPIN; + while (dcd_reg->ENDPTPRIME && guard--) {} + flush_endpoints(dcd_reg, 0xFFFFFFFF); +} + +/// Software-side reset handling, deferred to the port change ending the reset so the queue +/// heads stay coherent until the stack is told - and so a prime issued by a task that had +/// not yet seen BUS_RESET_START is flushed here rather than surviving re-enumeration. +static void bus_reset_complete(uint8_t rhport) { + ci_hs_regs_t *dcd_reg = CI_HS_REG(rhport); + flush_endpoints(dcd_reg, 0xFFFFFFFF); + + //------------- Queue Head & Queue TD -------------// + tu_memclr(&_dcd_data, sizeof(dcd_data_t)); + + //------------- Set up Control Endpoints (0 OUT, 1 IN) -------------// + _dcd_data.qhd[0][0].zero_length_termination = _dcd_data.qhd[0][1].zero_length_termination = 1; + _dcd_data.qhd[0][0].max_packet_size = _dcd_data.qhd[0][1].max_packet_size = CFG_TUD_ENDPOINT0_SIZE; + _dcd_data.qhd[0][0].qtd_overlay.next = _dcd_data.qhd[0][1].qtd_overlay.next = QTD_NEXT_INVALID; + + _dcd_data.qhd[0][0].int_on_setup = 1; // OUT only + + dcd_dcache_clean_invalidate(&_dcd_data, sizeof(dcd_data_t)); +} +``` + +- [ ] **Step 3: Route the two ISR branches to the new functions** + +In `dcd_int_handler()`, the `INTR_RESET` branch becomes: + +```c + if (int_status & INTR_RESET) { + bus_reset_begin(rhport); + _port_change_reason[rhport] = PORT_CHANGE_REASON_RESET; + dcd_event_bus_signal(rhport, DCD_EVENT_BUS_RESET_START, true); + } +``` + +and inside the `INTR_PORT_CHANGE` branch, the reset arm (the `else` of the resume test) +becomes: + +```c + } else { + bus_reset_complete(rhport); + // PSPD: 0 full, 1 low, 2 high, 3 undefined (treated as full) + const uint32_t pspd = (dcd_reg->PORTSC1 & PORTSC1_PORT_SPEED) >> PORTSC1_PORT_SPEED_POS; + const tusb_speed_t speed = (pspd == 1) ? TUSB_SPEED_LOW : (pspd == 2) ? TUSB_SPEED_HIGH : TUSB_SPEED_FULL; + dcd_event_bus_reset(rhport, speed, true); + } +``` + +Delete the now-unused EP0 `ENDPTFLUSH` line that previously sat at the top of that arm — +`bus_reset_complete()` flushes all endpoints. + +- [ ] **Step 4: Route the remaining flush sites through the helper** + +In `dcd_deinit()`, replace the flush block with: + +```c + // flush all endpoints + uint32_t guard = CI_HS_BUSY_SPIN; + while (dcd_reg->ENDPTPRIME && guard--) {} + flush_endpoints(dcd_reg, 0xFFFFFFFF); +``` + +In `dcd_edpt_iso_activate()`, replace the flush + spin with: + +```c + // Flush EP + flush_endpoints(dcd_reg, TU_BIT(epnum + (dir ? 16 : 0))); +``` + +- [ ] **Step 5: Build both ci_hs board families** + +Run: + +```bash +cmake --build examples/cmake-build-mimxrt1064_evk && cmake --build examples/cmake-build-lpcxpresso18s37 +``` + +Expected: both succeed with no new warnings. + +- [ ] **Step 6: Commit** + +```bash +git add src/portable/chipidea/ci_hs/dcd_ci_hs.c +git commit -m "dcd(ci_hs): report bus reset start at URI, finish at port change + +The RM wants the reset cleanup inside the reset window, but the negotiated +speed only exists once the port reaches its operational state, so the stack +was told nothing for the whole window - it kept believing it was configured +while the queue heads had been zeroed under it, and a transfer a class +driver started in that gap stayed primed across re-enumeration. + +Split the work along the register/software line: bus_reset_begin() does the +register cleanup at URI and signals BUS_RESET_START, bus_reset_complete() +re-flushes, resets the queue heads and reports BUS_RESET_END with the final +speed at the port change. Zeroing the queue heads now happens in the same +breath as telling the stack, and the second flush retires anything primed +in between. + +Fold the five hand-rolled endpoint flushes into one bounded helper while +the reset path is open." +``` + +--- + +### Task 3: Make the setup-time EP0 flush wait, and stop dropping the SET_ADDRESS status prime + +**Files:** +- Modify: `src/portable/chipidea/ci_hs/dcd_ci_hs.c` (`dcd_set_address()`; the `ENDPTSETUPSTAT` branch inside `dcd_int_handler()`) + +**Interfaces:** +- Consumes: `flush_endpoints()` (Task 2); `qhd_start_xfer()` returning `bool`, already propagated by `dcd_edpt_xfer()`. + +- [ ] **Step 1: Wait for the setup-time flush to complete** + +In the ISR's setup branch, replace the fire-and-forget flush line + +```c + dcd_reg->ENDPTFLUSH = TU_BIT(0) | TU_BIT(16); +``` + +with + +```c + // Wait it out: the flush retires a status/handshake phase left primed by the previous + // control sequence (UM10503 25.10.8.1.1), and an unfinished flush would otherwise + // still be asserted when the task primes the response to this setup and would retire + // that instead. A flush waits for any packet already in progress - microseconds at + // high speed - and the guard caps wedged hardware. + flush_endpoints(dcd_reg, TU_BIT(0) | TU_BIT(16)); +``` + +- [ ] **Step 2: Honour the status-prime result in `dcd_set_address`** + +Replace the body of `dcd_set_address()`: + +```c +void dcd_set_address(uint8_t rhport, uint8_t dev_addr) { + // Response with status first before changing device address. A refused prime means a new + // setup superseded this transfer; staging an address whose ACK will never arrive would + // leave the device answering on it, so only arm the address when the status went out. + if (dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0, false)) { + ci_hs_regs_t *dcd_reg = CI_HS_REG(rhport); + dcd_reg->DEVICEADDR = (dev_addr << 25) | TU_BIT(24); + } +} +``` + +- [ ] **Step 3: Build and commit** + +Run: `cmake --build examples/cmake-build-mimxrt1064_evk && cmake --build examples/cmake-build-lpcxpresso18s37` +Expected: both succeed. + +```bash +git add src/portable/chipidea/ci_hs/dcd_ci_hs.c +git commit -m "dcd(ci_hs): wait out the setup flush, honour the set-address prime + +The flush issued on every new setup was fire-and-forget. A flush waits for +a packet already in progress, so it could still be asserted when the task +primed the response to that setup and retire the fresh prime instead - +leaving EP0 silent until the host gave up. + +dcd_set_address() also armed DEVICEADDR unconditionally, but the status +prime can now be refused when a newer setup supersedes the transfer; the +address was then staged behind an ACK that never came and the device sat at +address 0. Only arm it when the status transfer actually started." +``` + +--- + +### Task 4: Emit RESUME only when the port really left suspend + +**Files:** +- Modify: `src/portable/chipidea/ci_hs/dcd_ci_hs.c` (the resume arm of the `INTR_PORT_CHANGE` branch in `dcd_int_handler()`) + +**Interfaces:** none consumed or produced. + +- [ ] **Step 1: Restore the hardware guard** + +In the `INTR_PORT_CHANGE` branch, the resume arm currently reads: + +```c + if (pci_reason == PORT_CHANGE_REASON_SUSPEND) { + dcd_event_bus_signal(rhport, DCD_EVENT_RESUME, true); + } else { +``` + +Replace that condition with one that also consults live hardware: + +```c + if (pci_reason == PORT_CHANGE_REASON_SUSPEND) { + // Only when the port actually left suspend: a starved snapshot can hold the resume's + // port change together with a second suspend, and reporting a resume there would + // leave the stack awake on a sleeping bus with no further event to correct it. + if (!(dcd_reg->PORTSC1 & PORTSC1_SUSPEND)) { + dcd_event_bus_signal(rhport, DCD_EVENT_RESUME, true); + } + } else { +``` + +- [ ] **Step 2: Build and commit** + +Run: `cmake --build examples/cmake-build-mimxrt1064_evk && cmake --build examples/cmake-build-lpcxpresso18s37` +Expected: both succeed. + +```bash +git add src/portable/chipidea/ci_hs/dcd_ci_hs.c +git commit -m "dcd(ci_hs): only report resume when the port left suspend + +A suspend, resume and second suspend collapsed into one interrupt pass +queued suspend then resume from the recorded cause alone, so the stack +ended up awake while the bus was still suspended and nothing arrived to +correct it. Consult PORTSC1 before reporting the resume." +``` + +--- + +### Task 5: ip3511 — never deliver a knowingly-torn setup packet + +**Files:** +- Modify: `src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c` (setup branch of `dcd_int_handler()`; the `dcd_edpt_clear_stall()` comment) + +**Interfaces:** none consumed or produced. + +- [ ] **Step 1: Deliver only when the copy is known good** + +Replace: + +```c + // a SETUP that raced in after the acks (its bit0 consumed above, this copy possibly torn): + // its latch is visible again - re-raise the endpoint interrupt so the next pass redelivers + // the newer payload + if (dcd_reg->DEVCMDSTAT & DEVCMDSTAT_SETUP_RECEIVED_MASK) { + dcd_reg->INTSETSTAT = TU_BIT(0); + } + + dcd_event_setup_received(rhport, setup_copy, true); +``` + +with: + +```c + // a SETUP that raced in after the acks (its bit0 consumed above) makes this copy suspect: + // its latch is visible again, so re-raise the endpoint interrupt and let the next pass + // deliver the newer payload rather than passing up bytes that may be torn between the two + if (dcd_reg->DEVCMDSTAT & DEVCMDSTAT_SETUP_RECEIVED_MASK) { + dcd_reg->INTSETSTAT = TU_BIT(0); + } else { + dcd_event_setup_received(rhport, setup_copy, true); + } +``` + +- [ ] **Step 2: Add the TODO token to the USB.13 deferral** + +In `dcd_edpt_clear_stall()`, change the caveat's opening line from + +```c + // Known caveat (Errata LPC546xx USB.13, same semantics in UM11126): with RF/TV preserved at 1, TR +``` + +to + +```c + // TODO implement the Errata LPC546xx USB.13 work-around (same semantics in UM11126): with RF/TV preserved at 1, TR +``` + +- [ ] **Step 3: Build and commit** + +Run: `cmake --build examples/cmake-build-lpcxpresso11u37 && cmake --build examples/cmake-build-lpcxpresso55s28` +Expected: both succeed. + +```bash +git add src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c +git commit -m "dcd(ip3511): drop a setup packet the hardware may have overwritten + +The handler already notices when a new setup landed while it was copying +the previous one, and re-raises the endpoint interrupt so the newer payload +is delivered next pass - but it then passed the suspect copy up anyway. +Usually harmless, since the redelivery supersedes it, but if that second +event cannot be queued the torn bytes are processed as a real request. +Deliver the copy only when no newer setup is pending." +``` + +--- + +### Task 6: BSP cleanups — dead RHPORT block and the stale linker comment + +**Files:** +- Modify: `hw/bsp/lpc55/boards/lpcxpresso55s28/board.cmake` +- Modify: `hw/bsp/lpc11/boards/lpcxpresso11u37/lpc11u37.ld` + +**Interfaces:** none consumed or produced. + +- [ ] **Step 1: Delete the redundant RHPORT block** + +`hw/bsp/lpc55/family.cmake` already applies the identical guarded defaults (`RHPORT_DEVICE 1`, +`RHPORT_HOST 0`) after including the board file, so remove these lines from +`board.cmake` entirely: + +```cmake +# device highspeed, host fullspeed; guarded so a -D override on the cmake command line wins +if (NOT DEFINED RHPORT_DEVICE) + set(RHPORT_DEVICE 1) +endif () +if (NOT DEFINED RHPORT_HOST) + set(RHPORT_HOST 0) +endif () +``` + +Leave `board.mk`'s `RHPORT_DEVICE ?= 1` / `RHPORT_HOST ?= 0` alone — `?=` is the idiomatic +Make form and matches sibling boards. + +- [ ] **Step 2: Prove the defaults and the override still work** + +Run: + +```bash +cd examples && rm -rf /tmp/rh-default /tmp/rh-override +cmake -B /tmp/rh-default -DBOARD=lpcxpresso55s28 -G Ninja . > /tmp/rh-default.log 2>&1 +grep -m1 "RHPORT_DEVICE" /tmp/rh-default.log || cmake -B /tmp/rh-default -DBOARD=lpcxpresso55s28 -G Ninja -LA . | grep -E "^RHPORT_(DEVICE|HOST)" +cmake -B /tmp/rh-override -DBOARD=lpcxpresso55s28 -DRHPORT_DEVICE=0 -DRHPORT_HOST=1 -G Ninja -LA . | grep -E "^RHPORT_(DEVICE|HOST)" +``` + +Expected: the default configure yields device 1 / host 0; the override configure yields +device 0 / host 1. Then rebuild the real tree: `cmake --build cmake-build-lpcxpresso55s28`. + +- [ ] **Step 3: Correct the linker-script comment and relabel the ASSERT** + +In `lpc11u37.ld`, replace the comment block above `__user_stack_top` and the ASSERT with: + +```text + /* Main (MSP/ISR) stack lives at the top of the USB SRAM bank: the 8K main bank is packed so + tight that only ~280 B remained above .bss, and ISR frames overflowed into the topmost task + stack (cdc_msc_freertos hard fault). Nothing else is placed in this bank in either build + system, so the stack owns all 2 KB; the ASSERT is future-proofing in case USB buffers are + ever mapped here again. */ + __user_stack_top = ORIGIN(RamUsb2) + LENGTH(RamUsb2); + ASSERT(__user_stack_top - (ADDR(.noinit_RAM2) + SIZEOF(.noinit_RAM2)) >= 0x200, + "main stack headroom in RamUsb2 below 512 bytes") +``` + +- [ ] **Step 4: Build both build systems for lpc11u37** + +Run: + +```bash +cmake --build examples/cmake-build-lpcxpresso11u37 +cd examples/device/cdc_msc_freertos && make -j8 BOARD=lpcxpresso11u37 all && cd ../../.. +``` + +Expected: both succeed. + +- [ ] **Step 5: Commit** + +```bash +git add hw/bsp/lpc55/boards/lpcxpresso55s28/board.cmake hw/bsp/lpc11/boards/lpcxpresso11u37/lpc11u37.ld +git commit -m "bsp: drop duplicated lpc55s28 rhport defaults, fix lpc11u37 comment + +hw/bsp/lpc55/family.cmake already applies the same guarded rhport defaults +after including the board file, so the board-level copy only added a second +place to keep in sync. + +The lpc11u37 linker comment still described USB buffers living in RamUsb2, +a placement the same branch removed; nothing lands there now, so say so and +label the headroom assert as future-proofing." +``` + +--- + +### Task 7: Stop halting the target when a DCD legitimately refuses a transfer + +**Files:** +- Modify: `src/device/usbd.c` (`usbd_edpt_xfer()` failure arm) + +**Interfaces:** none consumed or produced. + +- [ ] **Step 1: Remove the breakpoint from the DCD-refusal path** + +Replace the failure arm of `usbd_edpt_xfer()`: + +```c + } else { + // DCD error, mark endpoint as ready to allow next transfer + _usbd_dev.ep_status[epnum][dir] &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED); + TU_LOG_USBD("FAILED\r\n"); + TU_BREAKPOINT(); + return false; + } +``` + +with: + +```c + } else { + // Driver refused the transfer, mark endpoint as ready to allow next transfer. This is a + // recoverable condition (e.g. a new setup superseding a control response), not a bug, so + // do not break into the debugger - TU_BREAKPOINT() halts the CPU whenever a probe is + // attached, which on a test rig is always. + _usbd_dev.ep_status[epnum][dir] &= (uint8_t) ~(TU_EDPT_STATE_BUSY | TU_EDPT_STATE_CLAIMED); + TU_LOG_USBD("FAILED\r\n"); + return false; + } +``` + +- [ ] **Step 2: Confirm no other stack path relies on that breakpoint** + +Run: `grep -n "TU_BREAKPOINT" src/device/*.c src/device/*.h` +Expected: no remaining hits inside `usbd_edpt_xfer`; other occurrences (if any) are in +unrelated assert macros and stay as they are. + +- [ ] **Step 3: Build and run unit tests** + +Run: + +```bash +cmake --build examples/cmake-build-mimxrt1064_evk +cd test/unit-test && ceedling test:all && cd ../.. +``` + +Expected: build succeeds, all unit tests pass. + +- [ ] **Step 4: Commit** + +```bash +git add src/device/usbd.c +git commit -m "usbd: do not breakpoint when a driver refuses a transfer + +TU_BREAKPOINT() is not gated on CFG_TUSB_DEBUG - it halts the CPU whenever +a debugger is attached, which on a test rig is always. A driver declining a +transfer is recoverable (a new setup superseding a control response, for +one) and the endpoint is already released for the retry, so a halted target +turns a self-healing case into a dead board." +``` + +--- + +### Task 8: Full validation on hardware + +**Files:** none modified — this task produces the evidence for the PR description. + +**Interfaces:** consumes the firmware built by Tasks 1-7. + +- [ ] **Step 1: Software gate** + +Run: + +```bash +pre-commit run --all-files +cd examples +for b in mimxrt1064_evk lpcxpresso18s37 lpcxpresso11u37 lpcxpresso55s28; do + rm -rf cmake-build-$b && cmake -B cmake-build-$b -DBOARD=$b -G Ninja -DCMAKE_BUILD_TYPE=MinSizeRel . && cmake --build cmake-build-$b || echo "FAILED $b" +done +cd .. +``` + +Expected: pre-commit all green; all four boards build every example. + +- [ ] **Step 2: Make-build regression checks** + +Run: + +```bash +cd examples/host/cdc_msc_hid && make -j8 BOARD=lpcxpresso55s28 all && cd ../../.. +cd examples/device/cdc_msc_throughput && make -j8 BOARD=lpcxpresso11u37 all && cd ../../.. +``` + +Expected: both link (these two were broken earlier in the branch and are the regression +canaries for the BSP changes). + +- [ ] **Step 3: Flash with verification (mandatory)** + +The mimxrt1064_evk has twice accepted a flash that silently did not take, so every load in +this task uses `verifyfile`. For each board, write a J-Link script of this shape and run it: + +``` +r +h +loadfile examples/cmake-build-/device/usbtest/usbtest.elf +verifyfile examples/cmake-build-/device/usbtest/usbtest.elf +r +g +qc +``` + +Probes and devices: `mimxrt1064_evk` = `-USB 000725299165 -device MIMXRT1064xxx6A`, +`lpcxpresso55s28` = `-USB 000727031389 -device LPC55S28`, +`lpcxpresso11u37` = `-USB 000724441579 -device LPC11U37/401`. +Invoke as `JLinkExe -if swd -speed 4000 -autoconnect 1 -NoGui 1 -CommandFile