summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHiFiPHile <[email protected]>2026-08-25 09:27:48 +0200
committerHiFiPHile <[email protected]>2026-08-25 09:27:48 +0200
commitdfac26a272fa7bbbca2050fbe9f1ca09008e548e (patch)
treeefbc53f8f2c1e5d9c7f38e5fef6d774a20053cec /src
parente590b45fcf51f9ddace73178074e4fe6d691e319 (diff)
parent5c0e31cdabaf37f14e1f5e988a020abfc1000495 (diff)
Merge master updates into the UAC1 host branch
Bring the audio work onto the current host core and build files before applying the remaining review fixes. Signed-off-by: HiFiPHile <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/class/audio/audio_device.c5
-rw-r--r--src/class/cdc/cdc_host.c39
-rw-r--r--src/class/hid/hid.h2205
-rw-r--r--src/class/midi/midi2_device.c204
-rw-r--r--src/class/midi/midi2_device.h28
-rw-r--r--src/class/mtp/mtp_device.c18
-rw-r--r--src/class/net/ncm_device.c47
-rw-r--r--src/class/usbtmc/usbtmc_device.c21
-rw-r--r--src/class/usbtmc/usbtmc_device.h1
-rw-r--r--src/class/vendor/vendor_device.c620
-rw-r--r--src/class/vendor/vendor_device.h161
-rw-r--r--src/class/video/video_device.c3
-rw-r--r--src/common/tusb_mcu.h51
-rw-r--r--src/common/tusb_types.h2
-rw-r--r--src/device/dcd.h26
-rw-r--r--src/device/usbd.c135
-rw-r--r--src/device/usbd.h2
-rw-r--r--src/portable/chipidea/ci_fs/ci_fs_type.h54
-rw-r--r--src/portable/chipidea/ci_fs/dcd_ci_fs.c49
-rw-r--r--src/portable/chipidea/ci_fs/hcd_ci_fs.c (renamed from src/portable/nxp/khci/hcd_khci.c)265
-rw-r--r--src/portable/chipidea/ci_hs/ci_hs_imxrt.h3
-rw-r--r--src/portable/chipidea/ci_hs/ci_hs_lpc18_43.h5
-rw-r--r--src/portable/chipidea/ci_hs/ci_hs_type.h17
-rw-r--r--src/portable/chipidea/ci_hs/dcd_ci_hs.c251
-rw-r--r--src/portable/chipidea/ci_hs/hcd_ci_hs.c4
-rw-r--r--src/portable/mentor/musb/dcd_musb.c73
-rw-r--r--src/portable/mentor/musb/musb_max32.h1
-rw-r--r--src/portable/mentor/musb/musb_py32.h79
-rw-r--r--src/portable/mentor/musb/musb_ti.h1
-rw-r--r--src/portable/mentor/musb/musb_type.h71
-rw-r--r--src/portable/microchip/samd/dcd_samd.c44
-rw-r--r--src/portable/nordic/nrf5x/dcd_nrf5x.c98
-rw-r--r--src/portable/nxp/khci/dcd_khci.c560
-rw-r--r--src/portable/nxp/lpc17_40/dcd_lpc17_40.c281
-rw-r--r--src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c189
-rw-r--r--src/portable/raspberrypi/rp2040/dcd_rp2040.c46
-rw-r--r--src/portable/raspberrypi/rp2040/hcd_rp2040.c4
-rw-r--r--src/portable/raspberrypi/rp2040/rp2040_usb.c13
-rw-r--r--src/portable/raspberrypi/rp2040/rp2040_usb.h1
-rw-r--r--src/portable/renesas/rusb2/dcd_rusb2.c253
-rw-r--r--src/portable/renesas/rusb2/hcd_rusb2.c8
-rw-r--r--src/portable/renesas/rusb2/rusb2_ra.h42
-rw-r--r--src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c12
-rw-r--r--src/portable/st/stm32_fsdev/fsdev_apm32.h77
-rw-r--r--src/portable/st/stm32_fsdev/fsdev_at32.h2
-rw-r--r--src/portable/st/stm32_fsdev/fsdev_common.c5
-rw-r--r--src/portable/st/stm32_fsdev/fsdev_common.h2
-rw-r--r--src/portable/synopsys/dwc2/dcd_dwc2.c41
-rw-r--r--src/portable/wch/ch32_usbfs_reg.h8
-rw-r--r--src/portable/wch/dcd_ch32_usbfs.c109
-rw-r--r--src/portable/wch/dcd_ch32_usbhs.c12
-rw-r--r--src/tusb_option.h61
52 files changed, 4506 insertions, 1803 deletions
diff --git a/src/class/audio/audio_device.c b/src/class/audio/audio_device.c
index 12e82190e..bc4c7e544 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)
@@ -1844,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;
diff --git a/src/class/cdc/cdc_host.c b/src/class/cdc/cdc_host.c
index 2a452a43c..7a979f649 100644
--- a/src/class/cdc/cdc_host.c
+++ b/src/class/cdc/cdc_host.c
@@ -714,9 +714,13 @@ bool cdch_xfer_cb(uint8_t daddr, uint8_t ep_addr, xfer_result_t event, uint32_t
//--------------------------------------------------------------------+
// Enumeration
//--------------------------------------------------------------------+
+
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(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));
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;
@@ -1012,6 +1016,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(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);
p_cdc->serial_drid = SERIAL_DRIVER_ACM;
@@ -1020,8 +1026,15 @@ 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
+ // fully contained bLength >= 3 both keeps those reads valid and stops a zero-length descriptor from spinning
+ 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) &&
+ 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;
}
@@ -1031,23 +1044,29 @@ 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(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);
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 (TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) {
+ 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 = 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
+ // open_ep_stream_pair consumes exactly two endpoints; require that count and that both fit before reading them
+ 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 += data_itf->bNumEndpoints * sizeof(tusb_desc_endpoint_t);
+ p_desc += 2 * sizeof(tusb_desc_endpoint_t);
}
}
@@ -1189,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);
@@ -1574,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);
@@ -1745,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);
@@ -2081,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);
diff --git a/src/class/hid/hid.h b/src/class/hid/hid.h
index 50a206ced..a6d1dba15 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,1091 @@ 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_OCCLUSION = 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_SETTINGS_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_REDIALABLE_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_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,
- HID_USAGE_CONSUMER_PLUS_100 = 0x0021,
- HID_USAGE_CONSUMER_AM_PM = 0x0022,
+ 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_POWER = 0x0030, // OOC
+ HID_USAGE_CONSUMER_RESET = 0x0031, // OSC
+ HID_USAGE_CONSUMER_SLEEP = 0x0032, // OSC
- 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_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,
+ 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
- // Media Control
- HID_USAGE_CONSUMER_PLAY_PAUSE = 0x00CD,
+ // 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_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_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
+ // 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)
@@ -1371,6 +1935,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 +1996,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 +2579,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 +2687,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
diff --git a/src/class/midi/midi2_device.c b/src/class/midi/midi2_device.c
index 1d40a2efa..b0a9e2503 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,16 @@ 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
+ 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 {
midi2d_tx_t tx;
@@ -327,16 +341,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);
@@ -370,6 +388,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) {
@@ -380,6 +399,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 +441,122 @@ 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);
}
+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;
+
+ // 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));
+ uint16_t status = 0;
+ switch (bit) {
+ case 0x04: status = STREAM_EP_NAME; break;
+ case 0x08: status = STREAM_PROD_INSTANCE_ID; break;
+ default: break;
+ }
+
+ if (status != 0) {
+ if (!_nego_send_text(p_midi, status, 0)) return;
+ } 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;
+ }
+
+ 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;
+ }
+ 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 (p_midi->nego_pending_fb_filter & 0x02) {
+ if (!_nego_send_text(p_midi, STREAM_FB_NAME, f)) return;
+ }
+ 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 +579,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: {
@@ -437,14 +595,23 @@ static void _nego_handle_stream_msg(midi2d_interface_t* p_midi, const uint32_t*
}
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));
+ 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 (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 |= req_filter; // bit 0: FB Info, bit 1: FB Name
+ _nego_send_pending(p_midi);
break;
}
@@ -754,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;
@@ -824,6 +992,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
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);
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)
diff --git a/src/class/usbtmc/usbtmc_device.c b/src/class/usbtmc/usbtmc_device.c
index 07190d89f..0e9978a81 100644
--- a/src/class/usbtmc/usbtmc_device.c
+++ b/src/class/usbtmc/usbtmc_device.c
@@ -497,9 +497,24 @@ 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;
+ }
+ // 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;
#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
diff --git a/src/class/vendor/vendor_device.c b/src/class/vendor/vendor_device.c
index b3537b665..c4a550ef0 100644
--- a/src/class/vendor/vendor_device.c
+++ b/src/class/vendor/vendor_device.c
@@ -21,6 +21,28 @@ 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;
+ 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;
+ 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 +57,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 +77,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 +117,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 +204,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 +237,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 +255,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 +268,7 @@ uint32_t tud_vendor_n_write_available(uint8_t idx) {
return tu_edpt_stream_write_available(&p_itf->tx_stream);
#else
- // Non-FIFO mode
- TU_VERIFY(p_itf->ep_in > 0, 0); // must be opened
- return usbd_edpt_busy(p_itf->rhport, p_itf->ep_in) ? 0 : CFG_TUD_VENDOR_TX_EPSIZE;
+ return vendord_ep_write_available(p_itf, p_itf->ep_in, CFG_TUD_VENDOR_TX_EPSIZE);
#endif
}
@@ -174,6 +288,63 @@ bool tud_vendor_n_write_clear(uint8_t idx) {
#endif
//--------------------------------------------------------------------+
+// Interrupt endpoint API
+//--------------------------------------------------------------------+
+#if CFG_TUD_VENDOR_EP_INT_OUT
+bool tud_vendor_n_int_read_xfer(uint8_t idx) {
+ TU_VERIFY(idx < CFG_TUD_VENDOR);
+ vendord_interface_t *p_itf = &_vendord_itf[idx];
+ return vendord_ep_read_xfer(p_itf, p_itf->ep_int_out, _vendord_int_epbuf[idx].int_out, p_itf->int_rx_xfer_len);
+}
+#endif
+
+#if CFG_TUD_VENDOR_EP_INT_IN
+uint32_t tud_vendor_n_int_write(uint8_t idx, const void *buffer, uint32_t bufsize) {
+ TU_VERIFY(idx < CFG_TUD_VENDOR, 0);
+ vendord_interface_t *p_itf = &_vendord_itf[idx];
+ return vendord_ep_write(p_itf, p_itf->ep_int_in, _vendord_int_epbuf[idx].int_in, CFG_TUD_VENDOR_EP_INT_IN_BUFSIZE, buffer, bufsize);
+}
+
+uint32_t tud_vendor_n_int_write_available(uint8_t idx) {
+ TU_VERIFY(idx < CFG_TUD_VENDOR, 0);
+ vendord_interface_t *p_itf = &_vendord_itf[idx];
+ return vendord_ep_write_available(p_itf, p_itf->ep_int_in, CFG_TUD_VENDOR_EP_INT_IN_BUFSIZE);
+}
+#endif
+
+//--------------------------------------------------------------------+
+// Isochronous endpoint API
+//--------------------------------------------------------------------+
+#if CFG_TUD_VENDOR_EP_ISO_OUT
+bool tud_vendor_n_iso_read_xfer(uint8_t idx) {
+ TU_VERIFY(idx < CFG_TUD_VENDOR);
+ vendord_interface_t *p_itf = &_vendord_itf[idx];
+ return vendord_ep_read_xfer(p_itf, p_itf->ep_iso_out, _vendord_iso_epbuf[idx].iso_out, p_itf->iso_rx_xfer_len);
+}
+#endif
+
+#if CFG_TUD_VENDOR_EP_ISO_IN
+uint32_t tud_vendor_n_iso_write(uint8_t idx, const void *buffer, uint32_t bufsize) {
+ TU_VERIFY(idx < CFG_TUD_VENDOR, 0);
+ vendord_interface_t *p_itf = &_vendord_itf[idx];
+ return vendord_ep_write(p_itf, p_itf->ep_iso_in, _vendord_iso_epbuf[idx].iso_in, CFG_TUD_VENDOR_EP_ISO_IN_BUFSIZE, buffer, bufsize);
+}
+
+uint32_t tud_vendor_n_iso_write_available(uint8_t idx) {
+ TU_VERIFY(idx < CFG_TUD_VENDOR, 0);
+ vendord_interface_t *p_itf = &_vendord_itf[idx];
+ return vendord_ep_write_available(p_itf, p_itf->ep_iso_in, CFG_TUD_VENDOR_EP_ISO_IN_BUFSIZE);
+}
+#endif
+
+#if CFG_TUD_VENDOR_ALT_SETTINGS
+uint8_t tud_vendor_n_alt(uint8_t idx) {
+ TU_VERIFY(idx < CFG_TUD_VENDOR, 0);
+ return _vendord_itf[idx].cur_alt;
+}
+#endif
+
+//--------------------------------------------------------------------+
// USBD Driver API
//--------------------------------------------------------------------+
void vendord_init(void) {
@@ -232,17 +403,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 +472,287 @@ 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
+}
+
+// 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
+ 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 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
+ 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
+ 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
+ }
+ } 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;
+ 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
+ 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 +771,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 +834,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 +875,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
diff --git a/src/class/video/video_device.c b/src/class/video/video_device.c
index 3797e6b2b..770595178 100644
--- a/src/class/video/video_device.c
+++ b/src/class/video/video_device.c
@@ -1144,6 +1144,9 @@ 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 */
+ 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) {
diff --git a/src/common/tusb_mcu.h b/src/common/tusb_mcu.h
index 1f2afb03a..af43dfb12 100644
--- a/src/common/tusb_mcu.h
+++ b/src/common/tusb_mcu.h
@@ -126,6 +126,14 @@
#define CFG_TUSB_MEM_DCACHE_LINE_SIZE_DEFAULT 32
#endif
+ // Errata ERR050101, listed for RT1015/RT1020/RT1024/RT1050 (no fix scheduled) and for
+ // RT1060/RT1064 rev A (fixed in rev B); not listed for RT1010 or the RT11xx family.
+ #if defined(MIMXRT1015_SERIES) || defined(MIMXRT1021_SERIES) || defined(MIMXRT1024_SERIES) || \
+ defined(MIMXRT1051_SERIES) || defined(MIMXRT1052_SERIES) || defined(MIMXRT1061_SERIES) || \
+ defined(MIMXRT1062_SERIES) || defined(MIMXRT1064_SERIES)
+ #define CFG_TUSB_MIMXRT1XXX_ERRATA_ERR050101 1
+ #endif
+
#elif TU_CHECK_MCU(OPT_MCU_KINETIS_KL, OPT_MCU_KINETIS_K32L, OPT_MCU_KINETIS_K)
#define TUP_USBIP_CHIPIDEA_FS
#define TUP_USBIP_CHIPIDEA_FS_KINETIS
@@ -141,7 +149,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
@@ -673,7 +680,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
@@ -704,6 +713,25 @@
#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
+
+//--------------------------------------------------------------------+
+// 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
@@ -741,15 +769,24 @@
#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
+// Set by silicon whose isochronous IN endpoint can be unprimed by an IN token sent to that same
+// endpoint number on ANOTHER device sharing the host, taking one of this device's OUT endpoints
+// down with it - undetectable in software. Descriptors must then give an isochronous IN endpoint
+// a number no other device on the bus uses; a number is only safe while it stays unique, so two
+// affected boards on one hub must not pick the same one. Default 0 (no such conflict). Set it to
+// 0 by hand on RT1060/RT1064 rev B, which carry the fix - the revision cannot be told apart at
+// compile time, so the affected parts are assumed to be rev A.
+#ifndef CFG_TUSB_MIMXRT1XXX_ERRATA_ERR050101
+ #define CFG_TUSB_MIMXRT1XXX_ERRATA_ERR050101 0
+#endif
+
// Some USBIPs (SAMG, SAMX7X, PIC32, MAX3266x/MAX78002) cannot assign the same endpoint
// number to both IN and OUT. Default to 0 (same endpoint number may be used for IN and OUT).
#ifndef CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY
diff --git a/src/common/tusb_types.h b/src/common/tusb_types.h
index d49d277ed..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) ((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/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 7a6e13f8d..e84d72fa4 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);
@@ -455,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",
@@ -641,6 +643,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) {
@@ -694,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;
@@ -746,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,);
@@ -864,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;
@@ -918,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);
@@ -926,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;
@@ -1041,9 +1059,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 +1069,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 +1174,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 +1208,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 (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
+ if ( TUSB_REQ_CLEAR_FEATURE == p_request->bRequest ) {
+ usbd_edpt_clear_stall(rhport, ep_addr);
+ } else {
+ usbd_edpt_stall(rhport, ep_addr);
+ }
- // 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;
+ // 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;
- // 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;
@@ -1458,8 +1490,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);
+ 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);
+ }
}
}
@@ -1573,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;
}
}
@@ -1648,10 +1692,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) {
diff --git a/src/device/usbd.h b/src/device/usbd.h
index 9be12ed0a..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, (_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
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 0f3675349..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
{
@@ -175,6 +139,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);
diff --git a/src/portable/nxp/khci/hcd_khci.c b/src/portable/chipidea/ci_fs/hcd_ci_fs.c
index 209940656..a6f5405b5 100644
--- a/src/portable/nxp/khci/hcd_khci.c
+++ b/src/portable/chipidea/ci_fs/hcd_ci_fs.c
@@ -10,63 +10,28 @@
#if CFG_TUH_ENABLED && defined(TUP_USBIP_CHIPIDEA_FS)
-#ifdef TUP_USBIP_CHIPIDEA_FS_KINETIS
+#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_<family>.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 KHCI USB0
+ #define CI_FS_REG(_port) ((ci_fs_regs_t*) USB0_BASE)
+ #define CI_FS_IRQN USB0_IRQn
#else
#error "MCU is not supported"
#endif
-#include "host/hcd.h"
-#include "host/usbh.h"
+#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" );
+// TOK_PID_* and buffer_descriptor_t are shared with the device driver in ci_fs_type.h
typedef struct TU_ATTR_PACKED
{
@@ -107,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];
@@ -142,7 +110,9 @@ static int prepare_packets(int pipenum)
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);
+ // 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);
@@ -200,11 +170,11 @@ static bool continue_transfer(int pipenum, buffer_descriptor_t *bd)
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 */
+ 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 (KHCI->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ;
- KHCI->TOKEN = KHCI->TOKEN;
+ while (CI_REG->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ;
+ CI_REG->TOKEN = CI_REG->TOKEN;
}
return true;
}
@@ -215,13 +185,21 @@ static bool continue_transfer(int pipenum, buffer_descriptor_t *bd)
static bool resume_transfer(int pipenum)
{
int num_tokens = prepare_packets(pipenum);
- TU_ASSERT(0 <= num_tokens);
+ 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(USB0_IRQn);
- NVIC_DisableIRQ(USB0_IRQn);
+ const unsigned ie = NVIC_GetEnableIRQ(CI_FS_IRQN);
+ NVIC_DisableIRQ(CI_FS_IRQN);
pipe_state_t *pipe = &_hcd.pipe[pipenum];
- unsigned flags = KHCI->ENDPOINT[0].ENDPT & USB_ENDPT_HOSTWOHUB_MASK;
+ 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:
@@ -236,16 +214,16 @@ static bool resume_transfer(int pipenum)
}
// 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;
+ 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 (KHCI->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ;
- KHCI->TOKEN = token;
+ while (CI_REG->CTL & USB_CTL_TXSUSPENDTOKENBUSY_MASK) ;
+ CI_REG->TOKEN = token;
} while (--num_tokens);
- if (ie) NVIC_EnableIRQ(USB0_IRQn);
+ if (ie) NVIC_EnableIRQ(CI_FS_IRQN);
return true;
}
@@ -253,19 +231,38 @@ 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;
+ // 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);
- KHCI->INTEN |= USB_ISTAT_SOFTOK_MASK;
+ CI_REG->INT_EN |= USB_ISTAT_SOFTOK_MASK;
+ }
+}
+
+// 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;
- const unsigned s = KHCI->STAT;
- KHCI->ISTAT = USB_ISTAT_TOKDNE_MASK; /* fetch the next token if received */
+ 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;
@@ -295,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)
@@ -310,8 +313,9 @@ static void process_tokdne(uint8_t rhport)
}
_hcd.in_progress &= ~TU_BIT(pipenum);
pipe_state_t *pipe = &_hcd.pipe[ep->pipenum];
+ release_sibling_bd(s, pipe);
hcd_event_xfer_complete(pipe->dev_addr,
- tu_edpt_addr(KHCI->TOKEN & USB_TOKEN_TOKENENDPT_MASK, dir_in),
+ tu_edpt_addr(CI_REG->TOKEN & USB_TOKEN_TOKENENDPT_MASK, dir_in),
pipe->length - pipe->remaining,
result, true);
next_pipenum = select_next_pipenum(pipenum);
@@ -321,29 +325,32 @@ static void process_tokdne(uint8_t rhport)
static void process_attach(uint8_t rhport)
{
- unsigned ctl = KHCI->CTL;
+ unsigned ctl = CI_REG->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;
+ 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)
{
- 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;
+ 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;
+ // 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;
}
}
@@ -354,31 +361,31 @@ static void process_bus_reset(uint8_t rhport)
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);
+ CI_REG->USBTRC0 |= USB_USBTRC0_USBRESET_MASK;
+ while (CI_REG->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);
+ 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);
- KHCI->USBCTRL &= ~USB_USBCTRL_SUSP_MASK;
- KHCI->CTL |= USB_CTL_ODDRST_MASK;
+ CI_REG->USBCTRL &= ~USB_USBCTRL_SUSP_MASK;
+ CI_REG->CTL |= USB_CTL_ODDRST_MASK;
for (unsigned i = 0; i < 16; ++i) {
- KHCI->ENDPOINT[i].ENDPT = 0;
+ CI_REG->EP[i].CTL = 0;
}
- KHCI->CTL &= ~USB_CTL_ODDRST_MASK;
+ CI_REG->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;
+ 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(USB0_IRQn);
- KHCI->INTEN = USB_INTEN_ATTACHEN_MASK | USB_INTEN_TOKDNEEN_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;
- KHCI->ERREN = 0xff;
+ CI_REG->ERR_ENB = 0xff;
return true;
}
@@ -386,13 +393,13 @@ bool hcd_init(uint8_t rhport, const tusb_rhport_init_t* rh_init) {
void hcd_int_enable(uint8_t rhport)
{
(void)rhport;
- NVIC_EnableIRQ(USB0_IRQn);
+ NVIC_EnableIRQ(CI_FS_IRQN);
}
void hcd_int_disable(uint8_t rhport)
{
(void)rhport;
- NVIC_DisableIRQ(USB0_IRQn);
+ NVIC_DisableIRQ(CI_FS_IRQN);
}
uint32_t hcd_frame_number(uint8_t rhport)
@@ -401,8 +408,8 @@ uint32_t hcd_frame_number(uint8_t 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;
+ uint32_t frmnum = CI_REG->FRM_NUML;
+ frmnum |= CI_REG->FRM_NUMH << 8u;
return frmnum;
}
@@ -412,7 +419,7 @@ uint32_t hcd_frame_number(uint8_t rhport)
bool hcd_port_connect_status(uint8_t rhport)
{
(void)rhport;
- if (KHCI->ISTAT & USB_ISTAT_ATTACH_MASK)
+ if (CI_REG->INT_STAT & USB_ISTAT_ATTACH_MASK)
return true;
return false;
}
@@ -420,12 +427,12 @@ bool hcd_port_connect_status(uint8_t rhport)
void hcd_port_reset(uint8_t rhport)
{
(void)rhport;
- KHCI->CTL &= ~USB_CTL_USBENSOFEN_MASK;
- KHCI->CTL |= USB_CTL_RESET_MASK;
+ CI_REG->CTL &= ~USB_CTL_USBENSOFEN_MASK;
+ CI_REG->CTL |= USB_CTL_RESET_MASK;
unsigned cnt = SystemCoreClock / 100;
while (cnt--) __NOP();
- KHCI->CTL &= ~USB_CTL_RESET_MASK;
- KHCI->CTL |= USB_CTL_USBENSOFEN_MASK;
+ CI_REG->CTL &= ~USB_CTL_RESET_MASK;
+ CI_REG->CTL |= USB_CTL_USBENSOFEN_MASK;
_hcd.need_reset = false;
}
@@ -437,26 +444,26 @@ 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)
+ 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(USB0_IRQn);
+ 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(USB0_IRQn);
- NVIC_DisableIRQ(USB0_IRQn);
+ 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(USB0_IRQn);
+ if (ie) NVIC_EnableIRQ(CI_FS_IRQN);
}
//--------------------------------------------------------------------+
@@ -483,12 +490,12 @@ bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet
_hcd.in_progress |= TU_BIT(pipenum);
- unsigned hostwohub = KHCI->ENDPOINT[0].ENDPT & USB_ENDPT_HOSTWOHUB_MASK;
- KHCI->ENDPOINT[0].ENDPT = hostwohub |
+ 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;
- 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);
+ 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;
}
@@ -540,16 +547,16 @@ bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t *
TU_ASSERT(0 <= pipenum);
TU_ASSERT(0 == (_hcd.in_progress & TU_BIT(pipenum)));
- unsigned const ie = NVIC_GetEnableIRQ(USB0_IRQn);
- NVIC_DisableIRQ(USB0_IRQn);
+ 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 */
- KHCI->INTEN |= USB_ISTAT_SOFTOK_MASK;
- if (ie) NVIC_EnableIRQ(USB0_IRQn);
+ CI_REG->INT_EN |= USB_ISTAT_SOFTOK_MASK;
+ if (ie) NVIC_EnableIRQ(CI_FS_IRQN);
return true;
}
@@ -577,42 +584,42 @@ bool hcd_edpt_clear_stall(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr) {
void hcd_int_handler(uint8_t rhport, bool in_isr)
{
(void) in_isr;
- uint32_t is = KHCI->ISTAT;
- uint32_t msk = KHCI->INTEN;
+ uint32_t is = CI_REG->INT_STAT;
+ uint32_t msk = CI_REG->INT_EN;
// TU_LOG1("S %lx\r\n", is);
/* clear disabled interrupts */
- KHCI->ISTAT = (is & ~msk & ~USB_ISTAT_TOKDNE_MASK) | USB_ISTAT_SOFTOK_MASK;
+ CI_REG->INT_STAT = (is & ~msk & ~USB_ISTAT_TOKDNE_MASK) | USB_ISTAT_SOFTOK_MASK;
is &= msk;
if (is & USB_ISTAT_ERROR_MASK) {
- unsigned err = KHCI->ERRSTAT;
+ unsigned err = CI_REG->ERR_STAT;
if (err) {
TU_LOG1(" ERR %x\r\n", err);
- KHCI->ERRSTAT = err;
+ CI_REG->ERR_STAT = err;
} else {
- KHCI->INTEN &= ~USB_ISTAT_ERROR_MASK;
+ CI_REG->INT_EN &= ~USB_ISTAT_ERROR_MASK;
}
}
if (is & USB_ISTAT_USBRST_MASK) {
- KHCI->INTEN = (msk & ~USB_INTEN_USBRSTEN_MASK) | USB_INTEN_ATTACHEN_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) {
- KHCI->INTEN = (msk & ~USB_INTEN_ATTACHEN_MASK) | USB_INTEN_USBRSTEN_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) {
- KHCI->ISTAT = USB_ISTAT_STALL_MASK;
+ CI_REG->INT_STAT = USB_ISTAT_STALL_MASK;
}
if (is & USB_ISTAT_SOFTOK_MASK) {
msk &= ~USB_ISTAT_SOFTOK_MASK;
- KHCI->INTEN = msk;
+ CI_REG->INT_EN = msk;
if (_hcd.pending) {
int pipenum = __builtin_ctz(_hcd.pending);
_hcd.pending = 0;
diff --git a/src/portable/chipidea/ci_hs/ci_hs_imxrt.h b/src/portable/chipidea/ci_hs/ci_hs_imxrt.h
index f0f918fe2..8f0d6083e 100644
--- a/src/portable/chipidea/ci_hs/ci_hs_imxrt.h
+++ b/src/portable/chipidea/ci_hs/ci_hs_imxrt.h
@@ -36,6 +36,9 @@ static const ci_hs_controller_t _ci_controller[] =
#define CI_HS_REG(_port) ((ci_hs_regs_t*) _ci_controller[_port].reg_base)
+// 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)
#define CI_DCD_INT_DISABLE(_p) NVIC_DisableIRQ((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 f2061bd7a..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,4 +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)
+// 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..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),
@@ -71,11 +79,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 55906e678..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) {}
+ uint32_t guard = CI_HS_BUSY_SPIN;
+ while (dcd_reg->ENDPTPRIME && guard--) {}
+ dcd_reg->ENDPTFLUSH = 0xFFFFFFFFUL;
- // read reset bit in portsc
+ // 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,27 +267,29 @@ 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;
usbmode |= USBMODE_CM_DEVICE;
dcd_reg->USBMODE = usbmode;
+ #ifdef CI_HS_SET_AHB_BURST
+ CI_HS_SET_AHB_BURST(rhport);
+ #endif
+
#ifdef CFG_TUD_CI_HS_VBUS_CHARGE
dcd_reg->OTGSC = OTGSC_VBUS_CHARGE | OTGSC_OTG_TERMINATION;
#else
@@ -253,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
@@ -266,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;
@@ -276,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;
}
@@ -292,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) {
@@ -393,7 +464,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));
}
@@ -463,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);
@@ -491,25 +561,35 @@ 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];
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
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) {
@@ -525,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
@@ -578,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
@@ -628,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);
+ const uint8_t pci_reason = _port_change_reason[rhport]; // save current pci_reason
- // 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);
- }
- }
+ if (int_status & INTR_SUSPEND) {
+ _port_change_reason[rhport] = PORT_CHANGE_REASON_RESUME; // next PCI is resume
+ dcd_event_bus_signal(rhport, DCD_EVENT_SUSPEND, true);
}
- if (int_status & INTR_SUSPEND) {
- // TU_LOG2("Suspend %08lx\r\n", dcd_reg->PORTSC1);
+ // 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);
+ }
- 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);
- }
+ // 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));
@@ -672,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) { }
@@ -688,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);
}
}
diff --git a/src/portable/chipidea/ci_hs/hcd_ci_hs.c b/src/portable/chipidea/ci_hs/hcd_ci_hs.c
index 3cb69acfa..0f24f5bb6 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
+ #ifdef CI_HS_SET_AHB_BURST
+ CI_HS_SET_AHB_BURST(rhport);
+ #endif
+
#if !TUH_OPT_HIGH_SPEED
hcd_reg->PORTSC1 |= PORTSC1_FORCE_FULL_SPEED;
#endif
diff --git a/src/portable/mentor/musb/dcd_musb.c b/src/portable/mentor/musb/dcd_musb.c
index 1ebd1fe02..92c9fe92e 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,12 @@ 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) 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 };
+ 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.
// Force single-buffered on EP < 8 even if the caller requested DPB.
@@ -266,7 +282,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) {
@@ -292,6 +308,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 {
@@ -314,7 +336,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];
@@ -626,7 +648,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
}
/*------------------------------------------------------------------
@@ -635,6 +661,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);
@@ -651,6 +682,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
@@ -706,6 +738,18 @@ void dcd_remote_wakeup(uint8_t rhport) {
musb_regs->power &= ~MUSB_POWER_RESUME;
}
+#if defined(TUP_USBIP_MUSB_PY32)
+void dcd_connect(uint8_t rhport)
+{
+ (void) rhport;
+}
+
+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)
{
@@ -721,6 +765,8 @@ void dcd_disconnect(uint8_t rhport)
musb_regs->power &= ~MUSB_POWER_SOFTCONN;
}
+#endif
+
void dcd_sof_enable(uint8_t rhport, bool en)
{
(void) rhport;
@@ -744,6 +790,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);
@@ -751,7 +798,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) {
@@ -762,7 +809,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;
}
@@ -773,8 +820,11 @@ 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);
+ 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 ) {
@@ -790,6 +840,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);
@@ -797,7 +848,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) {
@@ -812,7 +863,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);
@@ -833,7 +884,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;
}
}
@@ -910,6 +961,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;
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/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)
diff --git a/src/portable/nordic/nrf5x/dcd_nrf5x.c b/src/portable/nordic/nrf5x/dcd_nrf5x.c
index dc85ff78e..5170e0645 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,58 +395,52 @@ 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;
+bool dcd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) {
+ (void)rhport;
+ (void)largest_packet_size;
+ // 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;
+ 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);
- 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);
- }
+ // 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 {
- _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;
+ 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;
}
- _dcd.xfer[epnum][dir].started = false;
+
__ISB();
__DSB();
+ return true;
}
-#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;
-}
-
-bool dcd_edpt_iso_activate(uint8_t rhport, const tusb_desc_endpoint_t *desc_ep) {
- (void)rhport;
- (void)desc_ep;
- return false;
-}
-#endif
-
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;
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/lpc17_40/dcd_lpc17_40.c b/src/portable/nxp/lpc17_40/dcd_lpc17_40.c
index 182710016..b577d0e9f 100644
--- a/src/portable/nxp/lpc17_40/dcd_lpc17_40.c
+++ b/src/portable/nxp/lpc17_40/dcd_lpc17_40.c
@@ -19,6 +19,12 @@
//--------------------------------------------------------------------+
#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. 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)
{
//------------- Word 0 -------------//
@@ -48,11 +54,37 @@ 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_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);
+}
+
+#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 +98,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 +117,29 @@ 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);
@@ -92,19 +153,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;
}
//--------------------------------------------------------------------+
@@ -117,6 +187,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
@@ -124,6 +199,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);
}
@@ -230,6 +307,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;
@@ -245,10 +323,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
@@ -267,6 +349,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;
}
@@ -281,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:
@@ -293,10 +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:
- TU_ASSERT((epnum % 3) == 0 && (epnum != 0) && (epnum != 15));
- break;
-
default:
break;
}
@@ -307,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
@@ -319,16 +397,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)
@@ -369,19 +485,28 @@ 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;
- _dcd.control.out_buffer = NULL;
- _dcd.control.out_bytes = 0;
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,
+ // 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;
+ usb_irq_unlock(lock);
}
}
@@ -406,26 +531,68 @@ 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
+ // 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);
+ }else
+ {
+ // Enable DMA
+ LPC_USB->EpDMAEn = TU_BIT(ep_id);
+ }
}
return true;
@@ -451,13 +618,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 +687,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 +768,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
diff --git a/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c b/src/portable/nxp/lpc_ip3511/dcd_lpc_ip3511.c
index aa0307d25..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 {
@@ -158,6 +162,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
@@ -167,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
@@ -176,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];
//--------------------------------------------------------------------+
@@ -217,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 ) ;
@@ -243,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
//--------------------------------------------------------------------+
@@ -280,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);
@@ -305,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)
@@ -317,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)
@@ -337,13 +362,37 @@ void dcd_sof_enable(uint8_t rhport, bool en)
//--------------------------------------------------------------------+
// DCD Endpoint Port
//--------------------------------------------------------------------+
+// 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 ) {
+ 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);
- _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)
@@ -352,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)
@@ -362,9 +419,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 +452,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;
@@ -399,34 +461,40 @@ 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;
}
}
-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: 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);
+ ep_cs[0].cmd_sts.active = 0;
+ ep_cs[1].cmd_sts.active = 0;
+ dcd_edpt_clear_stall(rhport, desc_ep->bEndpointAddress);
+ 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;
@@ -499,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
}
@@ -558,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
{
@@ -614,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);
- dcd_event_setup_received(rhport, _dcd.setup_packet, true);
+ // 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];
+ }
+
+ // 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
diff --git a/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/src/portable/raspberrypi/rp2040/dcd_rp2040.c
index 63097cd0a..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;
}
@@ -550,9 +553,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/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;
}
diff --git a/src/portable/raspberrypi/rp2040/rp2040_usb.c b/src/portable/raspberrypi/rp2040/rp2040_usb.c
index e4eb0184e..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;
@@ -176,10 +180,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) {
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) {
diff --git a/src/portable/renesas/rusb2/dcd_rusb2.c b/src/portable/renesas/rusb2/dcd_rusb2.c
index f0ef9738b..c93bab05e 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
@@ -40,6 +43,7 @@ typedef struct
static dcd_data_t _dcd;
+
//--------------------------------------------------------------------+
// INTERNAL OBJECT & FUNCTION DECLARATION
//--------------------------------------------------------------------+
@@ -121,9 +125,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;
}
//--------------------------------------------------------------------+
@@ -177,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);
@@ -201,6 +224,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 +255,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 +301,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);
@@ -333,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;
@@ -359,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 {
@@ -370,6 +425,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 +452,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
@@ -418,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);
}
@@ -448,7 +518,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,9 +536,7 @@ static void process_pipe_brdy(uint8_t rhport, unsigned num)
}
}
if (completed) {
- 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);
}
}
@@ -585,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;
@@ -702,10 +767,20 @@ 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);
}
- 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;
@@ -713,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;
@@ -748,6 +822,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 +833,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 +852,73 @@ 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);
+ rusb->PIPESEL = (uint16_t) num;
+ if (rusb2_is_highspeed_rhport(rhport)) {
+ // 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->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;
+ // 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;
}
-#endif
bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, bool is_isr)
{
@@ -799,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;
@@ -812,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;
@@ -847,7 +974,19 @@ 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) {
+ // 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
+ // 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;
}
}
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/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);
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_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..9c9233d12 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 = 1;
+ #endif
}
// De-initialize the USB Core
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/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c
index 6c88b4f27..b2f1a93a4 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);
@@ -1125,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);
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..ec521224e 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,38 @@ 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);
- data.isochronous[ep] = true;
+ // 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][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
+ 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;
-#endif
}
bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) {
@@ -510,7 +539,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 +575,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);
}
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;
diff --git a/src/tusb_option.h b/src/tusb_option.h
index 7a636657f..8fafde699 100644
--- a/src/tusb_option.h
+++ b/src/tusb_option.h
@@ -204,6 +204,12 @@
// HPMicro
#define OPT_MCU_HPM 2600 ///< HPMicro
+// 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))
@@ -240,6 +246,16 @@
#define OPT_MODE_SPEED_MASK 0xff00u
//--------------------------------------------------------------------+
+// 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 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
//--------------------------------------------------------------------+
@@ -253,6 +269,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
//--------------------------------------------------------------------+
@@ -350,7 +406,9 @@
#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
+ #if !defined(TUP_USBIP_MUSB_PY32)
+ #define CFG_TUSB_FIFO_HWFIFO_DATA_ODD_16BIT_ACCESS // allow odd 16bit 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
@@ -694,6 +752,7 @@
#ifndef CFG_TUH_ENUMERATION_BUFSIZE
#define CFG_TUH_ENUMERATION_BUFSIZE 256
#endif
+
#endif // CFG_TUH_ENABLED
// Attribute to place data in accessible RAM for host controller (default: CFG_TUSB_MEM_SECTION)