diff --git a/examples/host/cdc_msc_hid/CMakeLists.txt b/examples/host/cdc_msc_hid/CMakeLists.txt index c4a4d8e633..27dcdfd912 100644 --- a/examples/host/cdc_msc_hid/CMakeLists.txt +++ b/examples/host/cdc_msc_hid/CMakeLists.txt @@ -15,6 +15,9 @@ add_executable(${PROJECT}) # Example source target_sources(${PROJECT} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src/hid_app.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/hid_host_utils.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/hid_host_info.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/hid_host_joy.c ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c ${CMAKE_CURRENT_SOURCE_DIR}/src/msc_app.c ) diff --git a/examples/host/cdc_msc_hid/Makefile b/examples/host/cdc_msc_hid/Makefile index 272acbac8d..acb5da0969 100644 --- a/examples/host/cdc_msc_hid/Makefile +++ b/examples/host/cdc_msc_hid/Makefile @@ -16,6 +16,8 @@ CFLAGS += -Wno-error=cast-align -Wno-error=null-dereference SRC_C += \ src/class/cdc/cdc_host.c \ src/class/hid/hid_host.c \ + src/class/hid/hid_rip.c \ + src/class/hid/hid_ri.c \ src/class/msc/msc_host.c \ src/host/hub.c \ src/host/usbh.c \ diff --git a/examples/host/cdc_msc_hid/src/hid_app.c b/examples/host/cdc_msc_hid/src/hid_app.c index 11437c2b4a..edd7e14856 100644 --- a/examples/host/cdc_msc_hid/src/hid_app.c +++ b/examples/host/cdc_msc_hid/src/hid_app.c @@ -25,6 +25,8 @@ #include "bsp/board.h" #include "tusb.h" +#include "hid_host_joy.h" +#include "hid_host_info.h" //--------------------------------------------------------------------+ // MACRO TYPEDEF CONSTANT ENUM DECLARATION @@ -38,13 +40,6 @@ static uint8_t const keycode2ascii[128][2] = { HID_KEYCODE_TO_ASCII }; -// Each HID instance can has multiple reports -static struct -{ - uint8_t report_count; - tuh_hid_report_info_t report_info[MAX_REPORT]; -}hid_info[CFG_TUH_HID]; - static void process_kbd_report(hid_keyboard_report_t const *report); static void process_mouse_report(hid_mouse_report_t const * report); static void process_generic_report(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len); @@ -54,6 +49,61 @@ void hid_app_task(void) // nothing to do } +void handle_kbd_report(tusb_hid_host_info_t* info, const uint8_t* report, uint8_t report_length, uint8_t report_id) +{ + // Stop unused parameter errors + (void) info; + (void) report_length; + (void) report_id; + + TU_LOG1("HID receive keyboard report\r\n"); + process_kbd_report((hid_keyboard_report_t const *)report); +} + +void handle_mouse_report(tusb_hid_host_info_t* info, const uint8_t* report, uint8_t report_length, uint8_t report_id) +{ + // Stop unused parameter errors + (void) info; + (void) report_length; + (void) report_id; + + TU_LOG1("HID receive mouse report\r\n"); + process_mouse_report((hid_mouse_report_t const *)report); +} + +void handle_joystick_report(tusb_hid_host_info_t* info, const uint8_t* report, uint8_t report_length, uint8_t report_id) +{ + TU_LOG1("HID receive joystick report\r\n"); + tusb_hid_simple_joysick_t* simple_joystick = tuh_hid_get_simple_joystick( + info->key.elements.dev_addr, + info->key.elements.instance, + report_id); + + if (simple_joystick != NULL) { + tusb_hid_simple_joysick_process_report(simple_joystick, report, report_length); + tusb_hid_print_simple_joysick_report(simple_joystick); + } +} + +void handle_joystick_unmount(tusb_hid_host_info_t* info) { + TU_LOG1("HID joystick unmount\n"); + // Free up joystick definitions + tuh_hid_free_simple_joysticks_for_instance(info->key.elements.dev_addr, info->key.elements.instance); +} + +void handle_gamepad_report(tusb_hid_host_info_t* info, const uint8_t* report, uint8_t report_length, uint8_t report_id) +{ + // Stop unused parameter errors + (void) info; + (void) report_id; + + TU_LOG1("HID receive gamepad report "); + for(int i = 0; i < report_length; ++i) { + printf("%02x", report[i]); + } + printf("\r\n"); +} + //--------------------------------------------------------------------+ // TinyUSB Callbacks //--------------------------------------------------------------------+ @@ -67,6 +117,10 @@ void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_re { printf("HID device address = %d, instance = %d is mounted\r\n", dev_addr, instance); + printf("HID Report Description \r\n"); + for(int i = 0; i < desc_len; ++i) printf("%02X ", desc_report[i]); + printf("\r\n"); + // Interface protocol (hid_interface_protocol_enum_t) const char* protocol_str[] = { "None", "Keyboard", "Mouse" }; uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance); @@ -77,8 +131,50 @@ void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_re // Therefore for this simple example, we only need to parse generic report descriptor (with built-in parser) if ( itf_protocol == HID_ITF_PROTOCOL_NONE ) { - hid_info[instance].report_count = tuh_hid_parse_report_descriptor(hid_info[instance].report_info, MAX_REPORT, desc_report, desc_len); - printf("HID has %u reports \r\n", hid_info[instance].report_count); + tuh_hid_report_info_t reports[MAX_REPORT]; + uint8_t report_count = tuh_hid_parse_report_descriptor(reports, MAX_REPORT, desc_report, desc_len); + printf("HID has %u reports \r\n", report_count); + + for (uint8_t i = 0; i < report_count; ++i) { + tuh_hid_report_info_t *report = &reports[i]; + bool has_report_id = report_count > 1 || (report[0].report_id > 0); + + printf("HID report usage_page=%d, usage=%d, has_report_id=%d dev=%d instance=%d\n", report->usage_page, report->usage, has_report_id, dev_addr, instance); + + if (report->usage_page == HID_USAGE_PAGE_DESKTOP) + { + switch (report->usage) + { + case HID_USAGE_DESKTOP_KEYBOARD: { + printf("HID receive keyboard report description\r\n"); + tuh_hid_allocate_info(dev_addr, instance, has_report_id, &handle_kbd_report, NULL); + break; + } + case HID_USAGE_DESKTOP_JOYSTICK: { + printf("HID receive joystick report description\r\n"); + if(tuh_hid_allocate_info(dev_addr, instance, has_report_id, &handle_joystick_report, handle_joystick_unmount)) { + tuh_hid_joystick_parse_report_descriptor(desc_report, desc_len, dev_addr, instance); + } + break; + } + case HID_USAGE_DESKTOP_MOUSE: { + printf("HID receive mouse report description\r\n"); + tuh_hid_allocate_info(dev_addr, instance, has_report_id, &handle_mouse_report, NULL); + break; + } + case HID_USAGE_DESKTOP_GAMEPAD: { + printf("HID receive gamepad report description\r\n"); + tuh_hid_allocate_info(dev_addr, instance, has_report_id, &handle_gamepad_report, NULL); + // May be able to handle this in the same was as a the joystick. Needs a little investigation + break; + } + default: { + TU_LOG1("HID usage unknown usage:%d\r\n", report->usage); + break; + } + } + } + } } // request to receive report @@ -93,6 +189,9 @@ void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_re void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) { printf("HID device address = %d, instance = %d is unmounted\r\n", dev_addr, instance); + + // Invoke unmount functions adn free up host info structure + tuh_hid_free_info(dev_addr, instance); } // Invoked when received report from device via interrupt endpoint @@ -113,6 +212,7 @@ void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t cons break; default: + TU_LOG2("HID receive boot generic report\r\n"); // Generic report requires matching ReportID and contents with previous parsed report info process_generic_report(dev_addr, instance, report, len); break; @@ -232,65 +332,21 @@ static void process_mouse_report(hid_mouse_report_t const * report) //--------------------------------------------------------------------+ static void process_generic_report(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len) { - (void) dev_addr; - - uint8_t const rpt_count = hid_info[instance].report_count; - tuh_hid_report_info_t* rpt_info_arr = hid_info[instance].report_info; - tuh_hid_report_info_t* rpt_info = NULL; - - if ( rpt_count == 1 && rpt_info_arr[0].report_id == 0) - { - // Simple report without report ID as 1st byte - rpt_info = &rpt_info_arr[0]; - }else + tusb_hid_host_info_t* info = tuh_hid_get_info(dev_addr, instance); + + if (info == NULL) { - // Composite report, 1st byte is report ID, data starts from 2nd byte - uint8_t const rpt_id = report[0]; - - // Find report id in the arrray - for(uint8_t i=0; iusage_page == HID_USAGE_PAGE_DESKTOP ) - { - switch (rpt_info->usage) - { - case HID_USAGE_DESKTOP_KEYBOARD: - TU_LOG1("HID receive keyboard report\r\n"); - // Assume keyboard follow boot report layout - process_kbd_report( (hid_keyboard_report_t const*) report ); - break; - - case HID_USAGE_DESKTOP_MOUSE: - TU_LOG1("HID receive mouse report\r\n"); - // Assume mouse follow boot report layout - process_mouse_report( (hid_mouse_report_t const*) report ); - break; - - default: break; - } + if (info->has_report_id) { + rpt_id = report[0]; + report++; + len--; } + + info->handler(info, report, len, rpt_id); } diff --git a/examples/host/cdc_msc_hid/src/hid_host_info.c b/examples/host/cdc_msc_hid/src/hid_host_info.c new file mode 100644 index 0000000000..1f10ccaced --- /dev/null +++ b/examples/host/cdc_msc_hid/src/hid_host_info.c @@ -0,0 +1,95 @@ +/* + * The MIT License (MIT) + * + * 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. + * + */ + +#include "hid_host_info.h" + +static tusb_hid_host_info_t hid_info[TUP_DCD_ENDPOINT_MAX]; + +tusb_hid_host_info_t* tuh_hid_get_info(uint8_t dev_addr, uint8_t instance) +{ + tusb_hid_host_info_key_t key; + key.elements.dev_addr = dev_addr; + key.elements.instance = instance; + key.elements.in_use = 1; + uint32_t combined = key.combined; + + // Linear search for endpoint, which is fine while TUP_DCD_ENDPOINT_MAX + // is small. Perhaps a 'bsearch' version should be used if TUP_DCD_ENDPOINT_MAX + // is large. + for(uint8_t i = 0; i < TUP_DCD_ENDPOINT_MAX; ++i) { + tusb_hid_host_info_t* info = &hid_info[i]; + if (info->key.combined == combined) return info; + } + return NULL; +} + +void tuh_hid_free_sinlge_info(tusb_hid_host_info_t* info) +{ + if (info->key.elements.in_use && info->unmount != NULL) { + info->unmount(info); + } + info->key.elements.in_use = 0; +} + +void tuh_hid_free_all_info(void) +{ + for(uint8_t i = 0; i < TUP_DCD_ENDPOINT_MAX; ++i) { + tuh_hid_free_sinlge_info(&hid_info[i]); + } +} + +void tuh_hid_free_info(uint8_t dev_addr, uint8_t instance) +{ + for(uint8_t i = 0; i < TUP_DCD_ENDPOINT_MAX; ++i) { + tusb_hid_host_info_t* info = &hid_info[i]; + if (info->key.elements.instance == instance && info->key.elements.dev_addr == dev_addr && info->key.elements.in_use) { + tuh_hid_free_sinlge_info(info); + } + } +} + +tusb_hid_host_info_t* tuh_hid_allocate_info( + uint8_t dev_addr, + uint8_t instance, + bool has_report_id, + void (*handler)(struct tusb_hid_host_info* info, const uint8_t* report, uint8_t report_length, uint8_t report_id), + void (*unmount)(struct tusb_hid_host_info* info)) +{ + for(uint8_t i = 0; i < TUP_DCD_ENDPOINT_MAX; ++i) { + tusb_hid_host_info_t* info = &hid_info[i]; + if (!info->key.elements.in_use) { + tu_memclr(info, sizeof(tusb_hid_host_info_t)); + info->key.elements.in_use = true; + info->key.elements.dev_addr = dev_addr; + info->key.elements.instance = instance; + info->has_report_id = has_report_id; + info->handler = handler; + info->unmount = unmount; + return info; + } + } + TU_LOG1("FAILED TO ALLOCATE INFO for dev_addr=%d, instance=%d\r\n", dev_addr, instance); + return NULL; +} + + diff --git a/examples/host/cdc_msc_hid/src/hid_host_info.h b/examples/host/cdc_msc_hid/src/hid_host_info.h new file mode 100644 index 0000000000..29659d918f --- /dev/null +++ b/examples/host/cdc_msc_hid/src/hid_host_info.h @@ -0,0 +1,79 @@ +/* + * The MIT License (MIT) + * + * 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 module provides a place to store information about host endpoints. + * + * It records: + * A flag which indicates whether reports start with an identifier byte + * A 'handler' function to process a report + * An optional 'unmount' function called when the instance is removed + * + * Each 'info' record is indexed by the device address and instance number. + * + * [ It might be nice if the main library gave some support for managing + * state like this... particularly if it avoided more arrays & lookups ] + */ + +#ifndef _TUSB_HID_HOST_INFO_H_ +#define _TUSB_HID_HOST_INFO_H_ + +#include "tusb.h" + +#ifdef __cplusplus + extern "C" { +#endif + +typedef union TU_ATTR_PACKED +{ + uint32_t combined; + struct TU_ATTR_PACKED + { + uint8_t instance :8; + uint8_t dev_addr :8; + uint16_t in_use :16; + } elements; +} tusb_hid_host_info_key_t; + +typedef struct tusb_hid_host_info { + tusb_hid_host_info_key_t key; + bool has_report_id; + void (*handler)(struct tusb_hid_host_info* info, const uint8_t* report, uint8_t report_length, uint8_t report_id); + void (*unmount)(struct tusb_hid_host_info* info); +} tusb_hid_host_info_t; + +tusb_hid_host_info_t* tuh_hid_get_info(uint8_t dev_addr, uint8_t instance); + +tusb_hid_host_info_t* tuh_hid_allocate_info( + uint8_t dev_addr, + uint8_t instance, + bool has_report_id, + void (*handler)(struct tusb_hid_host_info* info, const uint8_t* report, uint8_t report_length, uint8_t report_id), + void (*unmount)(struct tusb_hid_host_info* info) +); + +void tuh_hid_free_info(uint8_t dev_addr, uint8_t instance); + +#ifdef __cplusplus +} +#endif + +#endif /* _TUSB_HID_HOST_INFO_H_ */ diff --git a/examples/host/cdc_msc_hid/src/hid_host_joy.c b/examples/host/cdc_msc_hid/src/hid_host_joy.c new file mode 100644 index 0000000000..70d50d1ef1 --- /dev/null +++ b/examples/host/cdc_msc_hid/src/hid_host_joy.c @@ -0,0 +1,288 @@ +/* + * The MIT License (MIT) + * + * 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. + * + * Test me with: + * + * ceedling test:pattern[hid_host_joy] + */ + +#include "hid_host_joy.h" +#include "hid_host_utils.h" + +static tusb_hid_simple_joysick_t hid_simple_joysicks[HID_MAX_JOYSTICKS]; + +static bool tuh_hid_joystick_check_usage(uint32_t eusage) +{ + // Check outer usage + switch(eusage) { + case HID_RIP_EUSAGE(HID_USAGE_PAGE_DESKTOP, HID_USAGE_DESKTOP_JOYSTICK): return true; + case HID_RIP_EUSAGE(HID_USAGE_PAGE_DESKTOP, HID_USAGE_DESKTOP_GAMEPAD): return true; // TODO not sure about this one + default: return false; + } +} + +tusb_hid_simple_joysick_t* tuh_hid_get_simple_joystick(uint8_t dev_addr, uint8_t instance, uint8_t report_id) +{ + tusb_hid_simple_joysick_key_t key; + key.elements.dev_addr = dev_addr; + key.elements.instance = instance; + key.elements.report_id = report_id; + key.elements.in_use = 1; + uint32_t combined = key.combined; + + for(uint8_t i = 0; i < HID_MAX_JOYSTICKS; ++i) { + tusb_hid_simple_joysick_t* simple_joystick = &hid_simple_joysicks[i]; + if (simple_joystick->key.combined == combined) return simple_joystick; + } + return NULL; +} + +void tuh_hid_free_simple_joysticks(void) { + for(uint8_t i = 0; i < HID_MAX_JOYSTICKS; ++i) { + hid_simple_joysicks[i].key.elements.in_use = false; + } +} + +void tuh_hid_free_simple_joysticks_for_instance(uint8_t dev_addr, uint8_t instance) { + for(uint8_t i = 0; i < HID_MAX_JOYSTICKS; ++i) { + tusb_hid_simple_joysick_t* simple_joystick = &hid_simple_joysicks[i]; + if (simple_joystick->key.elements.dev_addr == dev_addr && simple_joystick->key.elements.instance == instance) simple_joystick->key.elements.in_use = 0; + } +} + +tusb_hid_simple_joysick_t* tuh_hid_allocate_simple_joystick(uint8_t dev_addr, uint8_t instance, uint8_t report_id) { + for(uint8_t i = 0; i < HID_MAX_JOYSTICKS; ++i) { + tusb_hid_simple_joysick_t* simple_joystick = &hid_simple_joysicks[i]; + if (!simple_joystick->key.elements.in_use) { + tu_memclr(simple_joystick, sizeof(tusb_hid_simple_joysick_t)); + simple_joystick->key.elements.in_use = 1;; + simple_joystick->key.elements.instance = instance; + simple_joystick->key.elements.report_id = report_id; + simple_joystick->key.elements.dev_addr = dev_addr; + return simple_joystick; + } + } + return NULL; +} + +// get or create +tusb_hid_simple_joysick_t* tuh_hid_obtain_simple_joystick(uint8_t dev_addr, uint8_t instance, uint8_t report_id) { + tusb_hid_simple_joysick_t* jdata = tuh_hid_get_simple_joystick(dev_addr, instance, report_id); + return jdata ? jdata : tuh_hid_allocate_simple_joystick(dev_addr, instance, report_id); +} + +// Fetch some data from the HID parser +// +// The data fetched here may be relevant to multiple usage items +// +// returns false if obviously not of interest +bool tuh_hid_joystick_get_data( + tuh_hid_rip_state_t *pstate, // The current HID report parser state + const uint8_t* ri_input, // Pointer to the input item we have arrived at + tuh_hid_joystick_data_t* jdata) // Data structure to complete +{ + const uint8_t* ri_report_size = tuh_hid_rip_global(pstate, RI_GLOBAL_REPORT_SIZE); + const uint8_t* ri_report_count = tuh_hid_rip_global(pstate, RI_GLOBAL_REPORT_COUNT); + const uint8_t* ri_report_id = tuh_hid_rip_global(pstate, RI_GLOBAL_REPORT_ID); + const uint8_t* ri_logical_min = tuh_hid_rip_global(pstate, RI_GLOBAL_LOGICAL_MIN); + const uint8_t* ri_logical_max = tuh_hid_rip_global(pstate, RI_GLOBAL_LOGICAL_MAX); + const uint8_t* ri_usage_page = tuh_hid_rip_global(pstate, RI_GLOBAL_USAGE_PAGE); + const uint8_t* ri_usage_min = tuh_hid_rip_local(pstate, RI_LOCAL_USAGE_MIN); + const uint8_t* ri_usage_max = tuh_hid_rip_local(pstate, RI_LOCAL_USAGE_MAX); + + // We need to know how the size of the data + if (ri_report_size == NULL || ri_report_count == NULL || ri_usage_page == NULL) return false; + + jdata->report_size = tuh_hid_ri_short_udata32(ri_report_size); + jdata->report_count = tuh_hid_ri_short_udata32(ri_report_count); + jdata->report_id = ri_report_id ? tuh_hid_ri_short_udata8(ri_report_id) : 0; + jdata->logical_min = ri_logical_min ? tuh_hid_ri_short_data32(ri_logical_min) : 0; + jdata->logical_max = ri_logical_max ? tuh_hid_ri_short_data32(ri_logical_max) : 0; + jdata->input_flags.byte = tuh_hid_ri_short_udata8(ri_input); + jdata->usage_page = tuh_hid_ri_short_udata32(ri_usage_page); + jdata->usage_min = ri_usage_min ? tuh_hid_ri_short_udata32(ri_usage_min) : 0; + jdata->usage_max = ri_usage_max ? tuh_hid_ri_short_udata32(ri_usage_max) : 0; + jdata->usage_is_range = (ri_usage_min != NULL) && (ri_usage_max != NULL); + + return true; +} + +static void tuh_hid_joystick_process_axis( + tuh_hid_joystick_data_t* jdata, + uint32_t bitpos, + tusb_hid_simple_axis_t* simple_axis) +{ + simple_axis->start = bitpos; + simple_axis->length = jdata->report_size; + simple_axis->flags.is_signed = jdata->logical_min < 0; + simple_axis->logical_min = jdata->logical_min; + simple_axis->logical_max = jdata->logical_max; +} + +void tuh_hid_joystick_process_usages( + tuh_hid_rip_state_t *pstate, + tuh_hid_joystick_data_t* jdata, + uint32_t bitpos, + uint8_t dev_addr, + uint8_t instance) +{ + if (jdata->input_flags.data_const) return; + + // If there are no specific usages look for a range + // TODO What is the correct behaviour if there are both? + if (pstate->usage_count == 0 && !jdata->usage_is_range) { + printf("no usage - skipping bits %lu \n", jdata->report_size * jdata->report_count); + return; + } + + tusb_hid_simple_joysick_t* simple_joystick = tuh_hid_obtain_simple_joystick(dev_addr, instance, jdata->report_id); + + if (simple_joystick == NULL) { + printf("Failed to allocate joystick for HID dev_addr %d, instance %d, report ID %d\n", dev_addr, instance, jdata->report_id); + return; + } + + // Update the report length in bytes + simple_joystick->report_length = (uint8_t)((bitpos + (jdata->report_size * jdata->report_count) + 7) >> 3); + + // Naive, assumes buttons are defined in a range + if (jdata->usage_is_range) { + if (jdata->usage_page == HID_USAGE_PAGE_BUTTON) { + tusb_hid_simple_buttons_t* simple_buttons = &simple_joystick->buttons; + simple_buttons->start = bitpos; + simple_buttons->length = jdata->report_count; + return; + } + } + + for (uint8_t i = 0; i < jdata->report_count; ++i) { + uint32_t eusage = pstate->usages[i < pstate->usage_count ? i : pstate->usage_count - 1]; + switch (eusage) { + // Seems to be common usage for gamepads. + // Probably needs a lot more thought... + case HID_RIP_EUSAGE(HID_USAGE_PAGE_DESKTOP, HID_USAGE_DESKTOP_X): + tuh_hid_joystick_process_axis(jdata, bitpos, &simple_joystick->axis_x1); + break; + case HID_RIP_EUSAGE(HID_USAGE_PAGE_DESKTOP, HID_USAGE_DESKTOP_Y): + tuh_hid_joystick_process_axis(jdata, bitpos, &simple_joystick->axis_y1); + break; + case HID_RIP_EUSAGE(HID_USAGE_PAGE_DESKTOP, HID_USAGE_DESKTOP_Z): + tuh_hid_joystick_process_axis(jdata, bitpos, &simple_joystick->axis_x2); + break; + case HID_RIP_EUSAGE(HID_USAGE_PAGE_DESKTOP, HID_USAGE_DESKTOP_RZ): + tuh_hid_joystick_process_axis(jdata, bitpos, &simple_joystick->axis_y2); + break; + case HID_RIP_EUSAGE(HID_USAGE_PAGE_DESKTOP, HID_USAGE_DESKTOP_HAT_SWITCH): + tuh_hid_joystick_process_axis(jdata, bitpos, &simple_joystick->hat); + break; + default: break; + } + bitpos += jdata->report_size; + } +} + +void tuh_hid_joystick_parse_report_descriptor(uint8_t const* desc_report, uint16_t desc_len, uint8_t dev_addr, uint8_t instance) +{ + uint32_t eusage = 0; + tuh_hid_rip_state_t pstate; + tuh_hid_rip_init_state(&pstate, desc_report, desc_len); + const uint8_t *ri; + uint32_t bitpos = 0; + while((ri = tuh_hid_rip_next_short_item(&pstate)) != NULL) + { + uint8_t const type_and_tag = tuh_hid_ri_short_type_and_tag(ri); + + switch(type_and_tag) + { + case HID_RI_TYPE_AND_TAG(RI_TYPE_MAIN, RI_MAIN_INPUT): { + if (tuh_hid_joystick_check_usage(eusage)) { + tuh_hid_joystick_data_t joystick_data; + if(tuh_hid_joystick_get_data(&pstate, ri, &joystick_data)) { + tuh_hid_joystick_process_usages(&pstate, &joystick_data, bitpos, dev_addr, instance); + bitpos += joystick_data.report_size * joystick_data.report_count; + } + } + break; + } + case HID_RI_TYPE_AND_TAG(RI_TYPE_LOCAL, RI_LOCAL_USAGE): { + if (pstate.collections_count == 0) { + eusage = pstate.usages[pstate.usage_count - 1]; + } + break; + } + case HID_RI_TYPE_AND_TAG(RI_TYPE_GLOBAL, RI_GLOBAL_REPORT_ID): { + bitpos = 0; + break; + } + default: break; + } + } +} + +int32_t tuh_hid_simple_joystick_get_axis_value(tusb_hid_simple_axis_t* simple_axis, const uint8_t* report) +{ + return tuh_hid_report_i32(report, simple_axis->start, simple_axis->length, simple_axis->flags.is_signed); +} + +void tusb_hid_simple_joysick_process_report(tusb_hid_simple_joysick_t* simple_joystick, const uint8_t* report, uint8_t report_length) + { + if (simple_joystick->report_length > report_length) { + TU_LOG1("HID joystick report too small\r\n"); + return; + } + tusb_hid_simple_joysick_values_t* values = &simple_joystick->values; + values->x1 = tuh_hid_simple_joystick_get_axis_value(&simple_joystick->axis_x1, report); + values->y1 = tuh_hid_simple_joystick_get_axis_value(&simple_joystick->axis_y1, report); + values->x2 = tuh_hid_simple_joystick_get_axis_value(&simple_joystick->axis_x2, report); + values->y2 = tuh_hid_simple_joystick_get_axis_value(&simple_joystick->axis_y2, report); + values->hat = tuh_hid_simple_joystick_get_axis_value(&simple_joystick->hat, report); + values->buttons = tuh_hid_report_bits_u32(report, simple_joystick->buttons.start, simple_joystick->buttons.length); + simple_joystick->has_values = true; +} + +void tusb_hid_print_simple_joysick_report(tusb_hid_simple_joysick_t* simple_joystick) +{ + if (simple_joystick->has_values) { + printf("dev_addr=%3d, instance=%3d, report_id=%3d, x1=%4ld, y1=%4ld, x2=%4ld, y2=%4ld, hat=%01lX, buttons=%04lX\n", + simple_joystick->key.elements.dev_addr, + simple_joystick->key.elements.instance, + simple_joystick->key.elements.report_id, + simple_joystick->values.x1, + simple_joystick->values.y1, + simple_joystick->values.x2, + simple_joystick->values.y2, + simple_joystick->values.hat, + simple_joystick->values.buttons); + } +} + +uint8_t tuh_hid_get_simple_joysticks(tusb_hid_simple_joysick_t** simple_joysticks, uint8_t max_simple_joysticks) +{ + uint8_t j = 0; + for(uint8_t i = 0; i < HID_MAX_JOYSTICKS && j < max_simple_joysticks; ++i) { + tusb_hid_simple_joysick_t* simple_joystick = &hid_simple_joysicks[i]; + if (simple_joystick->key.elements.in_use) { + simple_joysticks[j++] = simple_joystick; + } + } + return j; +} + diff --git a/examples/host/cdc_msc_hid/src/hid_host_joy.h b/examples/host/cdc_msc_hid/src/hid_host_joy.h new file mode 100644 index 0000000000..09fb738f93 --- /dev/null +++ b/examples/host/cdc_msc_hid/src/hid_host_joy.h @@ -0,0 +1,193 @@ +/* + * The MIT License (MIT) + * + * 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 module contains an example of mapping a HID report to a simplified + * local joystick definition. This simple model has: + * 2x XY axes (X, Y and Z, Rz) + * 1x HAT control + * up to 32x Buttons (but have to be defined in one range in the HID description) + * + * Applications will still need to allow mapping of axis and buttons to + * particlar functions. + * + * There are many ways a HID report can describe a joystick and this code + * only copes with a few of them. + */ + +#ifndef _TUSB_HID_HOST_JOY_H_ +#define _TUSB_HID_HOST_JOY_H_ + +#include "tusb.h" +#include "class/hid/hid_rip.h" + +#ifdef __cplusplus + extern "C" { +#endif + +#define HID_MAX_JOYSTICKS 4 + +typedef union TU_ATTR_PACKED +{ + uint8_t byte; + struct TU_ATTR_PACKED + { + bool data_const : 1; + bool array_variable : 1; + bool absolute_relative : 1; + bool nowrap_wrap : 1; + bool linear_nonlinear : 1; + bool prefered_noprefered : 1; + bool nonull_null : 1; + }; +} tusb_hid_ri_intput_flags_t; + +typedef struct { + union TU_ATTR_PACKED + { + uint8_t byte; + struct TU_ATTR_PACKED + { + bool is_signed : 1; + }; + } flags; + uint16_t start; + uint16_t length; + int32_t logical_min; + int32_t logical_max; +} tusb_hid_simple_axis_t; + +typedef struct { + uint16_t start; + uint16_t length; +} tusb_hid_simple_buttons_t; + +// Very simple representation of a joystick to try and map to +// (and this will be quite tricky enough). +typedef struct { + int32_t x1; + int32_t y1; + int32_t x2; + int32_t y2; + int32_t hat; + uint32_t buttons; +} tusb_hid_simple_joysick_values_t; + +typedef union TU_ATTR_PACKED +{ + uint32_t combined; + struct TU_ATTR_PACKED + { + uint8_t instance :8; + uint8_t dev_addr :8; + uint8_t report_id :8; + uint8_t in_use :8; + } elements; +} tusb_hid_simple_joysick_key_t; + +// Simple joystick definitions and values +typedef struct { + tusb_hid_simple_joysick_key_t key; + uint8_t report_length; // requied report length in bytes + bool has_values; + tusb_hid_simple_axis_t axis_x1; + tusb_hid_simple_axis_t axis_y1; + tusb_hid_simple_axis_t axis_x2; + tusb_hid_simple_axis_t axis_y2; + tusb_hid_simple_axis_t hat; + tusb_hid_simple_buttons_t buttons; + tusb_hid_simple_joysick_values_t values; +} tusb_hid_simple_joysick_t; + +// Intermediate data structure used while parsing joystick HID report descriptors +typedef struct { + uint32_t report_size; + uint32_t report_count; + int32_t logical_min; + int32_t logical_max; + uint16_t usage_page; + uint16_t usage_min; + uint16_t usage_max; + uint8_t report_id; + tusb_hid_ri_intput_flags_t input_flags; + bool usage_is_range; +} tuh_hid_joystick_data_t; + +// Fetch some data from the HID parser +// +// The data fetched here may be relevant to multiple usage items +// +// returns false if obviously not of interest +bool tuh_hid_joystick_get_data( + tuh_hid_rip_state_t *pstate, // The current HID report parser state + const uint8_t* ri_input, // Pointer to the input item we have arrived at + tuh_hid_joystick_data_t* jdata // Data structure to complete +); + +// Process the HID descriptor usages +// These are handled when an 'input' item is encountered. +void tuh_hid_joystick_process_usages( + tuh_hid_rip_state_t *pstate, + tuh_hid_joystick_data_t* jdata, + uint32_t bitpos, + uint8_t dev_addr, + uint8_t instance +); + +// Parse a HID report description for a joystick +void tuh_hid_joystick_parse_report_descriptor(uint8_t const* desc_report, uint16_t desc_len, uint8_t dev_addr, uint8_t instance); + +// Fetch a previously allocated simple joystick +tusb_hid_simple_joysick_t* tuh_hid_get_simple_joystick(uint8_t dev_addr, uint8_t instance, uint8_t report_id); + +// Free a previously allocated simple joystick +void tuh_hid_free_simple_joysticks_for_instance(uint8_t dev_addr, uint8_t instance); + +// Free all previously allocated simple joysticks +void tuh_hid_free_simple_joysticks(void); + +// Allocate a new simple joystick +tusb_hid_simple_joysick_t* tuh_hid_allocate_simple_joystick(uint8_t dev_addr, uint8_t instance, uint8_t report_id); + +// If it exists, return an exisiting simple joystick, else allocate a new one +tusb_hid_simple_joysick_t* tuh_hid_obtain_simple_joystick(uint8_t dev_addr, uint8_t instance, uint8_t report_id); + +// Process a HID report +// The report pointer should be advanced beyond the report ID byte. +// The length should not include the report ID byte. +// The length should be in bytes. +void tusb_hid_simple_joysick_process_report(tusb_hid_simple_joysick_t* simple_joystick, const uint8_t* report, uint8_t report_length); + +// Send an axis and button report to stdout +// +// e.g. +// dev_addr= 1, instance= 0, report_id= 0, x1= 127, y1= 127, x2= 127, y2= 127, hat=F, buttons=0000 +// +void tusb_hid_print_simple_joysick_report(tusb_hid_simple_joysick_t* simple_joystick); + +// Populate an array of the attached joysticks +uint8_t tuh_hid_get_simple_joysticks(tusb_hid_simple_joysick_t** simple_joysticks, uint8_t max_simple_joysticks); + +#ifdef __cplusplus +} +#endif + +#endif /* _TUSB_HID_HOST_JOY_H_ */ diff --git a/examples/host/cdc_msc_hid/src/hid_host_utils.c b/examples/host/cdc_msc_hid/src/hid_host_utils.c new file mode 100644 index 0000000000..cbc60d5f5f --- /dev/null +++ b/examples/host/cdc_msc_hid/src/hid_host_utils.c @@ -0,0 +1,100 @@ +/* + * The MIT License (MIT) + * + * 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. + * + * Test me with: + * + * ceedling test:pattern[hid_host_utils] + */ + +#include "hid_host_utils.h" + +uint32_t tuh_hid_report_bits_u32(uint8_t const* report, uint16_t start, uint16_t length) +{ + const int16_t bit_offset_start = start & 7; + const int16_t l = length + bit_offset_start; + const uint8_t *p = report + (start >> 3); + uint32_t acc = ((uint32_t)*p++) >> bit_offset_start; + for(uint16_t i = 1; (i << 3) < l; ++i) { + acc |= ((uint32_t)*p++) << ((i << 3) - bit_offset_start); + } + const uint32_t lp0 = ((uint32_t)1) << (length - 1); + const uint32_t lp1 = lp0 << 1; + return acc & (lp1 - 1); +} + +int32_t tuh_hid_report_bits_i32(uint8_t const* report, uint16_t start, uint16_t length) +{ + const int16_t bit_offset_start = start & 7; + const int16_t l = length + bit_offset_start; + const uint8_t *p = report + (start >> 3); + uint32_t acc = ((uint32_t)*p++) >> bit_offset_start; + for(uint16_t i = 1; (i << 3) < l; ++i) { + acc |= ((uint32_t)*p++) << ((i << 3) - bit_offset_start); + } + const uint32_t lp0 = ((uint32_t)1) << (length - 1); + const uint32_t lp1 = lp0 << 1; + // Mask or sign extend + return acc & lp0 ? acc | -lp1 : acc & (lp1 - 1); +} + +// Helper to get some bytes from a HID report as an unsigned 32 bit number +uint32_t tuh_hid_report_bytes_u32(uint8_t const* report, uint16_t start, uint16_t length) +{ + const uint8_t *p = report + start; + if (length == 1) return (uint32_t)*p; + uint32_t acc = 0; + for(uint16_t i = 0; i < length; ++i) { + acc |= ((uint32_t)*p++) << (i << 3); + } + return acc; +} + +// Helper to get some bytes from a HID report as a signed 32 bit number +int32_t tuh_hid_report_bytes_i32(uint8_t const* report, uint16_t start, uint16_t length) +{ + const uint8_t *p = report + start; + if (length == 1) return (int32_t)(int8_t)*p; + uint32_t acc = 0; + for(uint16_t i = 0; i < length; ++i) { + acc |= ((uint32_t)*p++) << (i << 3); + } + const uint32_t lp0 = ((uint32_t)1) << ((length << 3) - 1); + const uint32_t lp1 = lp0 << 1; + // sign extend + return acc & lp0 ? acc | -lp1 : acc; +} + +// Helper to get a value from a HID report +int32_t tuh_hid_report_i32(const uint8_t* report, uint16_t start, uint16_t length, bool is_signed) +{ + if (length == 0) return 0; + if ((start | length) & 7) { + return is_signed ? + tuh_hid_report_bits_i32(report, start, length): + (int32_t)tuh_hid_report_bits_u32(report, start, length); + } + else { + return is_signed ? + tuh_hid_report_bytes_i32(report, start >> 3, length >> 3): + (int32_t)tuh_hid_report_bytes_u32(report, start >> 3, length >> 3); + } +} + diff --git a/examples/host/cdc_msc_hid/src/hid_host_utils.h b/examples/host/cdc_msc_hid/src/hid_host_utils.h new file mode 100644 index 0000000000..4661c13a3d --- /dev/null +++ b/examples/host/cdc_msc_hid/src/hid_host_utils.h @@ -0,0 +1,60 @@ +/* + * The MIT License (MIT) + * + * 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_HID_HOST_UTILS_H_ +#define _TUSB_HID_HOST_UTILS_H_ + +#include "tusb.h" +#include "class/hid/hid_rip.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Helpers for extracting values from a HID Report +//--------------------------------------------------------------------+ + +// Helper to get some bits from a HID report as an unsigned 32 bit number +uint32_t tuh_hid_report_bits_u32(uint8_t const* report, uint16_t start, uint16_t length); + +// Helper to get some bits from a HID report as a signed 32 bit number +int32_t tuh_hid_report_bits_i32(uint8_t const* report, uint16_t start, uint16_t length); + +// Helper to get some bytes from a HID report as an unsigned 32 bit number +uint32_t tuh_hid_report_bytes_u32(uint8_t const* report, uint16_t start, uint16_t length); + +// Helper to get some bytes from a HID report as a signed 32 bit number +int32_t tuh_hid_report_bytes_i32(uint8_t const* report, uint16_t start, uint16_t length); + +// Helper to get a value from a HID report +// Not suitable if the value can be > 2^31 +// Chooses between bytewise and bitwise fetches +int32_t tuh_hid_report_i32(const uint8_t* report, uint16_t start, uint16_t length, bool is_signed); + +#ifdef __cplusplus +} +#endif + +#endif /* _TUSB_HID_HOST_UTILS_H_ */ + diff --git a/examples/host/hid_controller/Makefile b/examples/host/hid_controller/Makefile index 06dc56914d..de8795b218 100644 --- a/examples/host/hid_controller/Makefile +++ b/examples/host/hid_controller/Makefile @@ -19,6 +19,8 @@ CFLAGS += -Wno-error=cast-align -Wno-error=null-dereference SRC_C += \ src/class/cdc/cdc_host.c \ src/class/hid/hid_host.c \ + src/class/hid/hid_ri.c \ + src/class/hid/hid_rip.c \ src/class/msc/msc_host.c \ src/host/hub.c \ src/host/usbh.c \ diff --git a/hw/bsp/rp2040/family.cmake b/hw/bsp/rp2040/family.cmake index 31e97d9af3..f7173b393c 100644 --- a/hw/bsp/rp2040/family.cmake +++ b/hw/bsp/rp2040/family.cmake @@ -91,6 +91,8 @@ if (NOT TARGET _rp2040_family_inclusion_marker) ${TOP}/src/host/usbh.c ${TOP}/src/host/hub.c ${TOP}/src/class/cdc/cdc_host.c + ${TOP}/src/class/hid/hid_ri.c + ${TOP}/src/class/hid/hid_rip.c ${TOP}/src/class/hid/hid_host.c ${TOP}/src/class/msc/msc_host.c ${TOP}/src/class/vendor/vendor_host.c diff --git a/src/class/hid/hid_host.c b/src/class/hid/hid_host.c index ca745464cb..f3f16160c6 100644 --- a/src/class/hid/hid_host.c +++ b/src/class/hid/hid_host.c @@ -32,6 +32,7 @@ #include "host/usbh_classdriver.h" #include "hid_host.h" +#include "hid_rip.h" //--------------------------------------------------------------------+ // MACRO CONSTANT TYPEDEF @@ -489,148 +490,6 @@ static void config_driver_mount_complete(uint8_t dev_addr, uint8_t instance, uin usbh_driver_set_config_complete(dev_addr, hid_itf->itf_num); } -//--------------------------------------------------------------------+ -// Report Descriptor Parser -//--------------------------------------------------------------------+ - -uint8_t tuh_hid_parse_report_descriptor(tuh_hid_report_info_t* report_info_arr, uint8_t arr_count, uint8_t const* desc_report, uint16_t desc_len) -{ - // Report Item 6.2.2.2 USB HID 1.11 - union TU_ATTR_PACKED - { - uint8_t byte; - struct TU_ATTR_PACKED - { - uint8_t size : 2; - uint8_t type : 2; - uint8_t tag : 4; - }; - } header; - - tu_memclr(report_info_arr, arr_count*sizeof(tuh_hid_report_info_t)); - - uint8_t report_num = 0; - tuh_hid_report_info_t* info = report_info_arr; - - // current parsed report count & size from descriptor -// uint8_t ri_report_count = 0; -// uint8_t ri_report_size = 0; - - uint8_t ri_collection_depth = 0; - - while(desc_len && report_num < arr_count) - { - header.byte = *desc_report++; - desc_len--; - - uint8_t const tag = header.tag; - uint8_t const type = header.type; - uint8_t const size = header.size; - - uint8_t const data8 = desc_report[0]; - - TU_LOG(3, "tag = %d, type = %d, size = %d, data = ", tag, type, size); - for(uint32_t i=0; iusage_page, desc_report, size); - break; - - case RI_GLOBAL_LOGICAL_MIN : break; - case RI_GLOBAL_LOGICAL_MAX : break; - case RI_GLOBAL_PHYSICAL_MIN : break; - case RI_GLOBAL_PHYSICAL_MAX : break; - - case RI_GLOBAL_REPORT_ID: - info->report_id = data8; - break; - - case RI_GLOBAL_REPORT_SIZE: -// ri_report_size = data8; - break; - - case RI_GLOBAL_REPORT_COUNT: -// ri_report_count = data8; - break; - - case RI_GLOBAL_UNIT_EXPONENT : break; - case RI_GLOBAL_UNIT : break; - case RI_GLOBAL_PUSH : break; - case RI_GLOBAL_POP : break; - - default: break; - } - break; - - case RI_TYPE_LOCAL: - switch(tag) - { - case RI_LOCAL_USAGE: - // only take in account the "usage" before starting REPORT ID - if ( ri_collection_depth == 0 ) info->usage = data8; - break; - - case RI_LOCAL_USAGE_MIN : break; - case RI_LOCAL_USAGE_MAX : break; - case RI_LOCAL_DESIGNATOR_INDEX : break; - case RI_LOCAL_DESIGNATOR_MIN : break; - case RI_LOCAL_DESIGNATOR_MAX : break; - case RI_LOCAL_STRING_INDEX : break; - case RI_LOCAL_STRING_MIN : break; - case RI_LOCAL_STRING_MAX : break; - case RI_LOCAL_DELIMITER : break; - default: break; - } - break; - - // error - default: break; - } - - desc_report += size; - desc_len -= size; - } - - for ( uint8_t i = 0; i < report_num; i++ ) - { - info = report_info_arr+i; - TU_LOG2("%u: id = %u, usage_page = %u, usage = %u\r\n", i, info->report_id, info->usage_page, info->usage); - } - - return report_num; -} - //--------------------------------------------------------------------+ // Helper //--------------------------------------------------------------------+ diff --git a/src/class/hid/hid_host.h b/src/class/hid/hid_host.h index fe09b03b2f..3cec5fe6a0 100644 --- a/src/class/hid/hid_host.h +++ b/src/class/hid/hid_host.h @@ -28,6 +28,7 @@ #define _TUSB_HID_HOST_H_ #include "hid.h" +#include "hid_rip.h" #ifdef __cplusplus extern "C" { @@ -46,18 +47,6 @@ #define CFG_TUH_HID_EPOUT_BUFSIZE 64 #endif - -typedef struct -{ - uint8_t report_id; - uint8_t usage; - uint16_t usage_page; - - // TODO still use the endpoint size for now -// uint8_t in_len; // length of IN report -// uint8_t out_len; // length of OUT report -} tuh_hid_report_info_t; - //--------------------------------------------------------------------+ // Interface API //--------------------------------------------------------------------+ @@ -71,10 +60,6 @@ bool tuh_hid_mounted(uint8_t dev_addr, uint8_t instance); // Get interface supported protocol (bInterfaceProtocol) check out hid_interface_protocol_enum_t for possible values uint8_t tuh_hid_interface_protocol(uint8_t dev_addr, uint8_t instance); -// Parse report descriptor into array of report_info struct and return number of reports. -// For complicated report, application should write its own parser. -uint8_t tuh_hid_parse_report_descriptor(tuh_hid_report_info_t* reports_info_arr, uint8_t arr_count, uint8_t const* desc_report, uint16_t desc_len) TU_ATTR_UNUSED; - //--------------------------------------------------------------------+ // Control Endpoint API //--------------------------------------------------------------------+ diff --git a/src/class/hid/hid_ri.c b/src/class/hid/hid_ri.c new file mode 100644 index 0000000000..a5a6c25526 --- /dev/null +++ b/src/class/hid/hid_ri.c @@ -0,0 +1,115 @@ +/* + * The MIT License (MIT) + * + * 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. + */ +#include "tusb_option.h" + +#if ((TUSB_OPT_HOST_ENABLED && CFG_TUH_HID) || _UNITY_TEST_) + +#include "hid_ri.h" + +uint8_t tuh_hid_ri_short_data_length(const uint8_t *ri) { + // 0 -> 0, 1 -> 1, 2 -> 2, 3 -> 4 + return (1 << (*ri & 3)) >> 1; +} + +uint8_t tuh_hid_ri_short_type(const uint8_t *ri) { + return (*ri >> 2) & 3; +} + +uint8_t tuh_hid_ri_short_tag(const uint8_t *ri) { + return (*ri >> 4) & 15; +} + +uint8_t tuh_hid_ri_short_type_and_tag(const uint8_t *ri) { + return (*ri) & 0xfc; +} + +bool tuh_hid_ri_is_long(const uint8_t *ri) { + return *ri == 0xfe; +} + +uint32_t tuh_hid_ri_short_udata32(const uint8_t *ri) { + uint32_t d = 0; + uint8_t l = tuh_hid_ri_short_data_length(ri++); + for(uint8_t i = 0; i < l; ++i) d |= ((uint32_t)(*ri++)) << (i << 3); + return d; +} + +uint8_t tuh_hid_ri_short_udata8(const uint8_t *ri) { + return tuh_hid_ri_short_data_length(ri) > 0 ? ri[1] : 0; +} + +int32_t tuh_hid_ri_short_data32(const uint8_t *ri) { + int32_t d = 0; + uint8_t l = tuh_hid_ri_short_data_length(ri++); + bool negative = false; + for(uint8_t i = 0; i < 4; ++i) { + if (i < l) { + uint32_t b = *ri++; + d |= b << (i << 3); + negative = ((b >> 7) == 1); + } + else if (negative) { + d |= 0xff << (i << 3); + } + } + return d; +} + +uint8_t tuh_hid_ri_long_data_length(const uint8_t *ri) { + return ri[1]; +} + +uint8_t tuh_hid_ri_long_tag(const uint8_t *ri) { + return ri[2]; +} + +const uint8_t* tuh_hid_ri_long_item_data(const uint8_t *ri) { + return ri + 3; +} + +int16_t tuh_hid_ri_size(const uint8_t *ri, uint16_t l) { + // Make sure there is enough room for the header + if (l < 1) return HID_RI_EOF; + // Calculate the short item length + uint16_t sl = 1 + tuh_hid_ri_short_data_length(ri); + // check it fits + if (l < sl) return HID_RI_ERR_MISSING_SHORT; + // Check if we need to worry about a long item + if (tuh_hid_ri_is_long(ri)) { + uint16_t ll = tuh_hid_ri_long_data_length(ri); + uint16_t tl = sl + ll; + if (l < tl) return HID_RI_ERR_MISSING_LONG; + return tl; + } + else { + return sl; + } +} + +void tuh_hid_ri_split_usage(uint32_t eusage, uint16_t *usage, uint16_t *usage_page) { + *usage = eusage & 0xffff; + *usage_page = eusage >> 16; +} + +#endif diff --git a/src/class/hid/hid_ri.h b/src/class/hid/hid_ri.h new file mode 100644 index 0000000000..1de4a79e96 --- /dev/null +++ b/src/class/hid/hid_ri.h @@ -0,0 +1,104 @@ +/* + * The MIT License (MIT) + * + * 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_HID_RI_H_ +#define _TUSB_HID_RI_H_ + +#include "tusb.h" + +#ifdef __cplusplus + extern "C" { +#endif + +#define HID_RI_TYPE_AND_TAG(TYPE, TAG) ((TAG << 4) | (TYPE << 2)) + +//--------------------------------------------------------------------+ +// HID Report Description Item functions +// +// Implementation intended to minimise memory footprint, +// mildly at the expense of performance. +// +// See: +// https://www.usb.org/sites/default/files/hid1_11.pdf +// https://eleccelerator.com/usbdescreqparser/ +// https://usb.org/sites/default/files/hut1_2.pdf +// https://eleccelerator.com/tutorial-about-usb-hid-report-descriptors/ +//--------------------------------------------------------------------+ + +// Get the length of a short item +uint8_t tuh_hid_ri_short_data_length(const uint8_t *ri); + +// Get the type of a short item +uint8_t tuh_hid_ri_short_type(const uint8_t *ri); + +// Get the tag from a short item +uint8_t tuh_hid_ri_short_tag(const uint8_t *ri); + +// Get the tag and type with length bits set to 0 +uint8_t tuh_hid_ri_short_type_and_tag(const uint8_t *ri); + +// Test if the item is a long item +bool tuh_hid_ri_is_long(const uint8_t *ri); + +// Get the short item data unsigned +uint32_t tuh_hid_ri_short_udata32(const uint8_t *ri); + +// Get the short item data unsigned +uint8_t tuh_hid_ri_short_udata8(const uint8_t *ri); + +// Get the short item data signed (with sign extend to uint32) +int32_t tuh_hid_ri_short_data32(const uint8_t *ri); + +// Get the data length of a long item +uint8_t tuh_hid_ri_long_data_length(const uint8_t *ri); + +// Get the tag from a long item +uint8_t tuh_hid_ri_long_tag(const uint8_t *ri); + +// Get a pointer to the data in a long item +const uint8_t* tuh_hid_ri_long_item_data(const uint8_t *ri); + +#define HID_RI_EOF 0 +#define HID_RI_ERR_MISSING_SHORT -1 +#define HID_RI_ERR_MISSING_LONG -2 +// Get the size of the item in bytes +// +// Important: +// To prevent buffer overflow call this before accessing short/long data +// and check the return code for eof or error. +// +// return values: +// HID_RI_EOF : no more values +// HID_RI_ERR_MISSING_SHORT : missing short bytes, +// HID_RI_ERR_MISSING_LONG : missing long bytes +int16_t tuh_hid_ri_size(const uint8_t *ri, uint16_t l); + +// Split an extended usage into local and page values +void tuh_hid_ri_split_usage(uint32_t eusage, uint16_t *usage, uint16_t *usage_page); + +#ifdef __cplusplus +} +#endif + +#endif /* _TUSB_HID_RI_H_ */ diff --git a/src/class/hid/hid_rip.c b/src/class/hid/hid_rip.c new file mode 100644 index 0000000000..e98a7f37fb --- /dev/null +++ b/src/class/hid/hid_rip.c @@ -0,0 +1,278 @@ +/* + * The MIT License (MIT) + * + * 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. + */ + +#include "tusb_option.h" + +#if ((TUSB_OPT_HOST_ENABLED && CFG_TUH_HID) || _UNITY_TEST_) + +#include "hid_rip.h" +#include "hid.h" + +void tuh_hid_rip_init_state(tuh_hid_rip_state_t *state, const uint8_t *report, uint16_t length) +{ + state->cursor = report; + state->length = length; + state->item_length = 0; + state->stack_index = 0; + state->usage_count = 0; + state->collections_count = 0; + tu_memclr(&state->global_items, sizeof(uint8_t*) * HID_REPORT_STACK_SIZE * 16); + tu_memclr(&state->local_items, sizeof(uint8_t*) * 16); + state->status = HID_RIP_INIT; +} + +const uint8_t* tuh_hid_rip_next_item(tuh_hid_rip_state_t *state) +{ + // Already at eof + if (state->length == 0) return NULL; + + const uint8_t *ri = state->cursor; + int16_t il = state->item_length; + + // Previous error encountered so do nothing + if (il < 0) return NULL; + + if (il > 0 && tuh_hid_ri_short_type(ri) == RI_TYPE_MAIN) { + // Clear down local state after a main item + tu_memclr(&state->local_items, sizeof(uint8_t*) * 16); + state->usage_count = 0; + } + + // Advance to the next report item + ri += il; + state->cursor = ri; + state->length -= il; + + // Normal eof + if (state->length == 0) { + state->item_length = 0; + state->status = HID_RIP_EOF; + return NULL; + } + + // Check the report item is valid + state->item_length = il = tuh_hid_ri_size(ri, state->length); + + if (il <= 0) { + state->status = HID_RIP_ITEM_ERR; + TU_LOG2("HID Report item parser: Attempt to read HID Report item returned %d\r\n", il); + return NULL; + } + + if (il > 0 && !tuh_hid_ri_is_long(ri)) { // For now ignore long items. + uint8_t short_type = tuh_hid_ri_short_type(ri); + uint8_t short_tag = tuh_hid_ri_short_tag(ri); + switch (short_type) { + case RI_TYPE_GLOBAL: + state->global_items[state->stack_index][short_tag] = ri; + switch (short_tag) { + case RI_GLOBAL_PUSH: + if (++state->stack_index == HID_REPORT_STACK_SIZE) { + state->status = HID_RIP_STACK_OVERFLOW; + TU_LOG2("HID Report item parser: stack overflow\r\n"); + return NULL; + } + memcpy(&state->global_items[state->stack_index], &state->global_items[state->stack_index - 1], sizeof(uint8_t*) * 16); + break; + case RI_GLOBAL_POP: + if (state->stack_index-- == 0) { + state->status = HID_RIP_STACK_UNDERFLOW; + TU_LOG2("HID Report item parser: stack underflow\r\n"); + return NULL; + } + break; + default: + break; + } + break; + case RI_TYPE_LOCAL: + switch(short_tag) { + case RI_LOCAL_USAGE: { + uint32_t usage = tuh_hid_ri_short_udata32(ri); + if (tuh_hid_ri_short_data_length(ri) <= 2) { + const uint8_t* usage_page_item = state->global_items[state->stack_index][RI_GLOBAL_USAGE_PAGE]; + uint32_t usage_page = usage_page_item ? tuh_hid_ri_short_udata32(usage_page_item) : 0; + usage |= usage_page << 16; + } + if (state->usage_count == HID_REPORT_MAX_USAGES) { + state->status = HID_RIP_USAGES_OVERFLOW; + TU_LOG2("HID Report item parser: usage overflow\r\n"); + return NULL; + } + state->usages[state->usage_count++] = usage; + break; + } + default: + state->local_items[short_tag] = ri; + break; + } + break; + case RI_TYPE_MAIN: { + switch(short_tag) { + case RI_MAIN_COLLECTION: { + if (state->collections_count == HID_REPORT_MAX_COLLECTION_DEPTH) { + state->status = HID_RIP_COLLECTIONS_OVERFLOW; + TU_LOG2("HID Report item parser: collections overflow\r\n"); + return NULL; + } + state->collections[state->collections_count++] = ri; + break; + } + case RI_MAIN_COLLECTION_END: + if (state->collections_count-- == 0) { + state->status = HID_RIP_COLLECTIONS_UNDERFLOW; + TU_LOG2("HID Report item parser: collections underflow\r\n"); + return NULL; + } + break; + default: + break; + } + break; + } + default: + break; + } + } + state->status = HID_RIP_TTEM_OK; + return ri; +} + +const uint8_t* tuh_hid_rip_next_short_item(tuh_hid_rip_state_t *state) +{ + const uint8_t* ri; + while((ri = tuh_hid_rip_next_item(state)) != NULL) if (!tuh_hid_ri_is_long(ri)) break; + return ri; +} + +const uint8_t* tuh_hid_rip_global(tuh_hid_rip_state_t *state, uint8_t tag) +{ + return state->global_items[state->stack_index][tag]; +} + +const uint8_t* tuh_hid_rip_local(tuh_hid_rip_state_t *state, uint8_t tag) +{ + return state->local_items[tag]; +} + +const uint8_t* tuh_hid_rip_current_item(tuh_hid_rip_state_t *state) +{ + return state->cursor; +} + +uint32_t tuh_hid_rip_report_total_size_bits(tuh_hid_rip_state_t *state) +{ + const uint8_t* ri_report_size = tuh_hid_rip_global(state, RI_GLOBAL_REPORT_SIZE); + const uint8_t* ri_report_count = tuh_hid_rip_global(state, RI_GLOBAL_REPORT_COUNT); + + if (ri_report_size != NULL && ri_report_count != NULL) + { + uint32_t report_size = tuh_hid_ri_short_udata32(ri_report_size); + uint32_t report_count = tuh_hid_ri_short_udata32(ri_report_count); + return report_size * report_count; + } + else + { + TU_LOG2("HID report parser cannot calc total report size in bits\r\n"); + return 0; + } +} + +//--------------------------------------------------------------------+ +// Report Descriptor Parser +//--------------------------------------------------------------------+ +uint8_t tuh_hid_parse_report_descriptor(tuh_hid_report_info_t* report_info_arr, uint8_t arr_count, uint8_t const* desc_report, uint16_t desc_len) +{ + // Prepare the summary array + tu_memclr(report_info_arr, arr_count*sizeof(tuh_hid_report_info_t)); + uint8_t report_num = 0; + uint16_t usage = 0; + uint16_t usage_page = 0; + + tuh_hid_report_info_t* info = report_info_arr; + tuh_hid_rip_state_t pstate; + tuh_hid_rip_init_state(&pstate, desc_report, desc_len); + const uint8_t *ri; + while((ri = tuh_hid_rip_next_short_item(&pstate)) != NULL) + { + uint8_t const type_and_tag = tuh_hid_ri_short_type_and_tag(ri); + + switch(type_and_tag) + { + case HID_RI_TYPE_AND_TAG(RI_TYPE_MAIN, RI_MAIN_INPUT): { + if (report_num >= arr_count) { + TU_LOG1("HID report description contains more than the maximum %d reports\r\n", arr_count); + return report_num; + } + info->in_len += tuh_hid_rip_report_total_size_bits(&pstate); + info->usage = usage; + info->usage_page = usage_page; + break; + } + case HID_RI_TYPE_AND_TAG(RI_TYPE_MAIN, RI_MAIN_OUTPUT): { + if (report_num >= arr_count) { + TU_LOG1("HID report description contains more than the maximum %d reports\r\n", arr_count); + return report_num; + } + info->out_len += tuh_hid_rip_report_total_size_bits(&pstate); + info->usage = usage; + info->usage_page = usage_page; + break; + } + case HID_RI_TYPE_AND_TAG(RI_TYPE_GLOBAL, RI_GLOBAL_REPORT_ID): { + if (report_num >= arr_count) { + TU_LOG1("HID report description contains more than the maximum %d reports\r\n", arr_count); + return report_num; + } + if (info->report_id > 0 || info->in_len > 0 || info->out_len > 0) { + info++; + report_num++; + } + info->report_id = tuh_hid_ri_short_udata8(ri); + break; + } + case HID_RI_TYPE_AND_TAG(RI_TYPE_LOCAL, RI_LOCAL_USAGE): { + // only take into account the "usage" before starting REPORT ID + if ( pstate.collections_count == 0 ) { + uint32_t eusage = pstate.usages[pstate.usage_count - 1]; + tuh_hid_ri_split_usage(eusage, &usage, &usage_page); + } + break; + } + default: break; + } + } + + for ( uint8_t i = 0; i < report_num + 1; i++ ) + { + info = report_info_arr+i; + TU_LOG2("%u: id = %02X, usage_page = %04X, usage = %04X, in_len = %u, out_len = %u\r\n", i, info->report_id, info->usage_page, info->usage, info->in_len, info->out_len); + } + + return report_num + 1; +} + +#endif + + diff --git a/src/class/hid/hid_rip.h b/src/class/hid/hid_rip.h new file mode 100644 index 0000000000..951aa1a47b --- /dev/null +++ b/src/class/hid/hid_rip.h @@ -0,0 +1,154 @@ +/* + * The MIT License (MIT) + * + * 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_HID_RIP_H_ +#define _TUSB_HID_RIP_H_ + +#include "tusb.h" +#include "hid_ri.h" +#include "hid.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// HID Report Description Parser functions +// +// Implementation intended to minimise memory footprint, +// mildly at the expense of performance. +// +// Iterates through report items and manages state rules. +// +// Expected usage: +// +// tuh_hid_rip_state_t pstate; +// tuh_hid_rip_init_state(&pstate, desc_report, desc_len); +// const uint8_t *ri; +// while((ri = tuh_hid_rip_next_item(&pstate)) != NULL) +// { +// // ri points to the current hid report item +// ... +// } +// +// See: +// https://www.usb.org/sites/default/files/hid1_11.pdf +// https://eleccelerator.com/usbdescreqparser/ +// https://usb.org/sites/default/files/hut1_2.pdf +// https://eleccelerator.com/tutorial-about-usb-hid-report-descriptors/ +//--------------------------------------------------------------------+ + +#define HID_REPORT_STACK_SIZE 10 +#define HID_REPORT_MAX_USAGES 20 +#define HID_REPORT_MAX_COLLECTION_DEPTH 20 + +#define HID_RIP_EUSAGE(G, L) ((G << 16) | L) + + +typedef enum tuh_hid_rip_status { + HID_RIP_INIT = 0, // Initial state + HID_RIP_EOF, // No more items + HID_RIP_TTEM_OK, // Last item parsed ok + HID_RIP_ITEM_ERR, // Issue decoding a single report item + HID_RIP_STACK_OVERFLOW, // Too many pushes + HID_RIP_STACK_UNDERFLOW, // Too many pops + HID_RIP_USAGES_OVERFLOW, // Too many usages + HID_RIP_COLLECTIONS_OVERFLOW, // Too many collections + HID_RIP_COLLECTIONS_UNDERFLOW // More collection ends than starts +} tuh_hid_rip_status_t; + +typedef struct tuh_hid_rip_state { + const uint8_t* cursor; + uint16_t length; + int16_t item_length; + uint8_t stack_index; + uint8_t usage_count; + uint8_t collections_count; + const uint8_t* global_items[HID_REPORT_STACK_SIZE][16]; + const uint8_t* local_items[16]; + const uint8_t* collections[HID_REPORT_MAX_COLLECTION_DEPTH]; + uint32_t usages[HID_REPORT_MAX_USAGES]; + tuh_hid_rip_status_t status; +} tuh_hid_rip_state_t; + + +typedef struct +{ + uint8_t report_id; + uint16_t usage; + uint16_t usage_page; + + // TODO still use the endpoint size for now + // Note: these currently do not include the Report ID byte + uint16_t in_len; // length of IN report in bits + uint16_t out_len; // length of OUT report in bits +} tuh_hid_report_info_t; + + +// Initialise a report item descriptor parser +void tuh_hid_rip_init_state(tuh_hid_rip_state_t *state, const uint8_t *report, uint16_t length); + +// Move to the next item in the report +// +// returns pointer to next item or null +// +const uint8_t* tuh_hid_rip_next_item(tuh_hid_rip_state_t *state); + +// Move to the next short item in the report +// +// returns pointer to next item or null +// +const uint8_t* tuh_hid_rip_next_short_item(tuh_hid_rip_state_t *state); + +// Accessor for the current value of a global item +// +// Returns a pointer to the start of the item of null +const uint8_t* tuh_hid_rip_global(tuh_hid_rip_state_t *state, uint8_t tag); + +// Accessor for the current value of a local item +// +// Returns a pointer to the start of the item of null +const uint8_t* tuh_hid_rip_local(tuh_hid_rip_state_t *state, uint8_t tag); + +// Returns a pointer to the start of the item or NULL for eof +const uint8_t* tuh_hid_rip_current_item(tuh_hid_rip_state_t *state); + +// Return report_size * report_count +// +// Note: this currently does not include the Report ID byte +uint32_t tuh_hid_rip_report_total_size_bits(tuh_hid_rip_state_t *state); + +//--------------------------------------------------------------------+ +// Report Descriptor Parser +// +// Parse report descriptor into array of report_info struct and return number of reports. +// For complicated report, application should write its own parser. +//--------------------------------------------------------------------+ +uint8_t tuh_hid_parse_report_descriptor(tuh_hid_report_info_t* reports_info_arr, uint8_t arr_count, uint8_t const* desc_report, uint16_t desc_len) TU_ATTR_UNUSED; + +#ifdef __cplusplus +} +#endif + +#endif /* _TUSB_HID_RIP_H_ */ diff --git a/test/project.yml b/test/project.yml index dc4a9cb28c..4f6f4284ad 100644 --- a/test/project.yml +++ b/test/project.yml @@ -36,6 +36,7 @@ - -:test/support :source: - ../src/** + - ../examples/host/cdc_msc_hid/* :support: - test/support diff --git a/test/test/class/hid/test_hid_ri.c b/test/test/class/hid/test_hid_ri.c new file mode 100644 index 0000000000..27ab5985cd --- /dev/null +++ b/test/test/class/hid/test_hid_ri.c @@ -0,0 +1,163 @@ +/* + * The MIT License (MIT) + * + * 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. + */ + + +#include "unity.h" + +#include "hid_ri.h" +TEST_FILE("hid_ri.c") + +void setUp(void) +{ +} + +void tearDown(void) +{ +} + +//--------------------------------------------------------------------+ +// Tests +//--------------------------------------------------------------------+ +void test_short_item_length(void) +{ + uint8_t tb[] = { 0x00, 0x01, 0x02, 0x03 }; + + TEST_ASSERT_EQUAL(0, tuh_hid_ri_short_data_length(&tb[0])); + TEST_ASSERT_EQUAL(1, tuh_hid_ri_short_data_length(&tb[1])); + TEST_ASSERT_EQUAL(2, tuh_hid_ri_short_data_length(&tb[2])); + TEST_ASSERT_EQUAL(4, tuh_hid_ri_short_data_length(&tb[3])); +} + +void test_short_item_size_check(void) +{ + uint8_t tb[] = { 0x46, 0x3B, 0x01 }; /* Physical Maximum (315) */ + + TEST_ASSERT_EQUAL( 3, tuh_hid_ri_size(tb, 3)); + TEST_ASSERT_EQUAL(-1, tuh_hid_ri_size(tb, 2)); + TEST_ASSERT_EQUAL(-1, tuh_hid_ri_size(tb, 1)); + TEST_ASSERT_EQUAL( 0, tuh_hid_ri_size(tb, 0)); +} + +void test_long_item(void) +{ + uint8_t tb[] = { 0xfe, 0xff, 0x81, 0x01 }; + + TEST_ASSERT_EQUAL(true, tuh_hid_ri_is_long(tb)); + TEST_ASSERT_EQUAL(2, tuh_hid_ri_short_data_length(tb)); + TEST_ASSERT_EQUAL(255, tuh_hid_ri_long_data_length(tb)); + TEST_ASSERT_EQUAL(0x81, tuh_hid_ri_long_tag(tb)); + TEST_ASSERT_EQUAL(1, tuh_hid_ri_long_item_data(tb)[0]); + TEST_ASSERT_EQUAL(3 + 255, tuh_hid_ri_size(tb, 3 + 255)); +} + +void test_long_item_size_check(void) +{ + uint8_t tb[] = { 0xfe, 0xff, 0x81, 0x01 }; + + TEST_ASSERT_EQUAL(3 + 255, tuh_hid_ri_size(tb, 3 + 255)); + TEST_ASSERT_EQUAL(-2, tuh_hid_ri_size(tb, 3 + 254)); + TEST_ASSERT_EQUAL(-2, tuh_hid_ri_size(tb, 3)); + TEST_ASSERT_EQUAL(-1, tuh_hid_ri_size(tb, 2)); + TEST_ASSERT_EQUAL(-1, tuh_hid_ri_size(tb, 1)); + TEST_ASSERT_EQUAL( 0, tuh_hid_ri_size(tb, 0)); +} + +void test_physical_max_315(void) +{ + uint8_t tb[] = { 0x46, 0x3B, 0x01 }; /* Physical Maximum (315) */ + + TEST_ASSERT_EQUAL(2, tuh_hid_ri_short_data_length(tb)); + TEST_ASSERT_EQUAL(1, tuh_hid_ri_short_type(tb)); + TEST_ASSERT_EQUAL(4, tuh_hid_ri_short_tag(tb)); + TEST_ASSERT_EQUAL(HID_RI_TYPE_AND_TAG(1, 4), tuh_hid_ri_short_type_and_tag(tb)); + TEST_ASSERT_EQUAL(false, tuh_hid_ri_is_long(tb)); + TEST_ASSERT_EQUAL(315, tuh_hid_ri_short_udata32(tb)); + TEST_ASSERT_EQUAL(315, tuh_hid_ri_short_data32(tb)); + TEST_ASSERT_EQUAL(3, tuh_hid_ri_size(tb, 3)); +} + +void test_physical_max_123(void) { + uint8_t tb[] = { 0x46, 0x7B, 0x00 }; /* Physical Maximum (123) */ + + TEST_ASSERT_EQUAL(2, tuh_hid_ri_short_data_length(tb)); + TEST_ASSERT_EQUAL(1, tuh_hid_ri_short_type(tb)); + TEST_ASSERT_EQUAL(4, tuh_hid_ri_short_tag(tb)); + TEST_ASSERT_EQUAL(false, tuh_hid_ri_is_long(tb)); + TEST_ASSERT_EQUAL(123, tuh_hid_ri_short_udata32(tb)); + TEST_ASSERT_EQUAL(123, tuh_hid_ri_short_udata8(tb)); + TEST_ASSERT_EQUAL(123, tuh_hid_ri_short_data32(tb)); + TEST_ASSERT_EQUAL(3, tuh_hid_ri_size(tb, 3)); +} + +void test_logical_min_neg_127(void) +{ + uint8_t tb[] = { 0x15, 0x81 }; /* LOGICAL_MINIMUM (-127) */ + + TEST_ASSERT_EQUAL(1, tuh_hid_ri_short_data_length(tb)); + TEST_ASSERT_EQUAL(1, tuh_hid_ri_short_type(tb)); + TEST_ASSERT_EQUAL(1, tuh_hid_ri_short_tag(tb)); + TEST_ASSERT_EQUAL(HID_RI_TYPE_AND_TAG(1, 1), tuh_hid_ri_short_type_and_tag(tb)); + TEST_ASSERT_EQUAL(false, tuh_hid_ri_is_long(tb)); + TEST_ASSERT_EQUAL(0x81, tuh_hid_ri_short_udata32(tb)); + TEST_ASSERT_EQUAL(-127, tuh_hid_ri_short_data32(tb)); + TEST_ASSERT_EQUAL(2, tuh_hid_ri_size(tb, 2)); +} + +void test_logical_min_neg_32768(void) +{ + uint8_t tb[] = { 0x16, 0x00, 0x80 }; // Logical Minimum (-32768) + + TEST_ASSERT_EQUAL(2, tuh_hid_ri_short_data_length(tb)); + TEST_ASSERT_EQUAL(1, tuh_hid_ri_short_type(tb)); + TEST_ASSERT_EQUAL(1, tuh_hid_ri_short_tag(tb)); + TEST_ASSERT_EQUAL(false, tuh_hid_ri_is_long(tb)); + TEST_ASSERT_EQUAL(-32768, tuh_hid_ri_short_data32(tb)); + TEST_ASSERT_EQUAL(3, tuh_hid_ri_size(tb, 3)); +} + +// https://eleccelerator.com/usbdescreqparser/ says this should be -2147483648, +// which I am pretty sure is wrong.. but worth noting in case problems arise later. +void test_logical_min_neg_2147483647(void) +{ + uint8_t tb[] = { 0x17, 0x01, 0x00, 0x00, 0x80 }; // Logical Minimum (-2147483647) + + TEST_ASSERT_EQUAL(4, tuh_hid_ri_short_data_length(tb)); + TEST_ASSERT_EQUAL(1, tuh_hid_ri_short_type(tb)); + TEST_ASSERT_EQUAL(1, tuh_hid_ri_short_tag(tb)); + TEST_ASSERT_EQUAL(false, tuh_hid_ri_is_long(tb)); + TEST_ASSERT_EQUAL(-2147483647, tuh_hid_ri_short_data32(tb)); + TEST_ASSERT_EQUAL(5, tuh_hid_ri_size(tb, 5)); +} + +void test_split_usage(void) { + uint16_t usage; + uint16_t usage_page; + + tuh_hid_ri_split_usage(0xFA23832B, &usage, &usage_page); + + TEST_ASSERT_EQUAL(0x832B, usage); + TEST_ASSERT_EQUAL(0xFA23, usage_page); +} + + diff --git a/test/test/class/hid/test_hid_rip.c b/test/test/class/hid/test_hid_rip.c new file mode 100644 index 0000000000..1f336e38d2 --- /dev/null +++ b/test/test/class/hid/test_hid_rip.c @@ -0,0 +1,708 @@ +/* + * The MIT License (MIT) + * + * 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. + */ + + +#include "unity.h" + +// Files to test +#include "hid_rip.h" +TEST_FILE("hid_ri.c") +TEST_FILE("hid_rip.c") + +void setUp(void) +{ +} + +void tearDown(void) +{ +} + +//--------------------------------------------------------------------+ +// Tests +//--------------------------------------------------------------------+ +void test_next_simple(void) +{ + uint8_t tb[] = { + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x02, // Usage (Mouse) + 0xA1, 0x01, // Collection (Application) + }; + + tuh_hid_rip_state_t pstate; + tuh_hid_rip_init_state(&pstate, (uint8_t*)&tb, sizeof(tb)); + TEST_ASSERT_EQUAL(&tb[0], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(2, pstate.item_length); + TEST_ASSERT_EQUAL(0x05, *pstate.cursor); + TEST_ASSERT_EQUAL(&tb[2], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(2, pstate.item_length); + TEST_ASSERT_EQUAL(0x09, *pstate.cursor); + TEST_ASSERT_EQUAL(&tb[4], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(2, pstate.item_length); + TEST_ASSERT_EQUAL(0xA1, *pstate.cursor); + TEST_ASSERT_EQUAL(NULL, tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(0, pstate.item_length); +} + +void test_usage_with_page(void) +{ + uint8_t tb[] = { + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x02, // Usage (Mouse) + }; + + tuh_hid_rip_state_t pstate; + tuh_hid_rip_init_state(&pstate, (uint8_t*)&tb, sizeof(tb)); + TEST_ASSERT_EQUAL(&tb[0], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(0x01, tuh_hid_ri_short_udata32(pstate.cursor)); + TEST_ASSERT_EQUAL(&tb[2], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(1, pstate.usage_count); + TEST_ASSERT_EQUAL(0x02, tuh_hid_ri_short_udata32(pstate.cursor)); + TEST_ASSERT_EQUAL(0x00010002, pstate.usages[0]); +} + +void test_globals_recorded(void) +{ + uint8_t tb[] = { + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x15, 0x55, // Logical Minimum 55 + }; + + tuh_hid_rip_state_t pstate; + tuh_hid_rip_init_state(&pstate, (uint8_t*)&tb, sizeof(tb)); + TEST_ASSERT_EQUAL(&tb[0], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(&tb[2], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(0, pstate.stack_index); + TEST_ASSERT_EQUAL(0x01, tuh_hid_ri_short_udata32(pstate.global_items[pstate.stack_index][0])); + TEST_ASSERT_EQUAL(0x55, tuh_hid_ri_short_data32(pstate.global_items[pstate.stack_index][1])); +} + +void test_globals_overwritten(void) +{ + uint8_t tb[] = { + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x05, 0x09, // USAGE_PAGE (Button) + }; + + tuh_hid_rip_state_t pstate; + tuh_hid_rip_init_state(&pstate, (uint8_t*)&tb, sizeof(tb)); + TEST_ASSERT_EQUAL(&tb[0], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(&tb[2], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(0, pstate.stack_index); + TEST_ASSERT_EQUAL(0x09, tuh_hid_ri_short_udata32(tuh_hid_rip_global(&pstate, 0))); +} + +void test_push_pop(void) +{ + uint8_t tb[] = { + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0xA4, // Push + 0x05, 0x09, // USAGE_PAGE (Button) + 0xB4, // Pop + }; + + tuh_hid_rip_state_t pstate; + tuh_hid_rip_init_state(&pstate, (uint8_t*)&tb, sizeof(tb)); + TEST_ASSERT_EQUAL(&tb[0], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(&tb[2], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(&tb[3], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(&tb[5], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(0, pstate.stack_index); + TEST_ASSERT_EQUAL(0x01, tuh_hid_ri_short_udata32(tuh_hid_rip_global(&pstate, 0))); +} + +void test_stack_overflow(void) +{ + uint8_t tb[HID_REPORT_STACK_SIZE]; + memset(tb, 0xA4, sizeof(tb)); // Push + + tuh_hid_rip_state_t pstate; + tuh_hid_rip_init_state(&pstate, (uint8_t*)&tb, sizeof(tb)); + TEST_ASSERT_EQUAL(pstate.status, HID_RIP_INIT); + for (int i = 0; i < HID_REPORT_STACK_SIZE - 1; ++i) { + TEST_ASSERT_EQUAL(&tb[i], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(pstate.status, HID_RIP_TTEM_OK); + } + TEST_ASSERT_EQUAL(NULL, tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(pstate.status, HID_RIP_STACK_OVERFLOW); +} + +void test_stack_underflow(void) +{ + uint8_t tb[] = { + 0xA4, // Push + 0xB4, // Pop + 0xB4, // Pop + }; + + tuh_hid_rip_state_t pstate; + tuh_hid_rip_init_state(&pstate, (uint8_t*)&tb, sizeof(tb)); + TEST_ASSERT_EQUAL(pstate.status, HID_RIP_INIT); + TEST_ASSERT_EQUAL(&tb[0], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(pstate.status, HID_RIP_TTEM_OK); + TEST_ASSERT_EQUAL(&tb[1], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(pstate.status, HID_RIP_TTEM_OK); + TEST_ASSERT_EQUAL(0, tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(pstate.status, HID_RIP_STACK_UNDERFLOW); +} + +void test_usage_overflow(void) +{ + uint8_t tb[HID_REPORT_MAX_USAGES + 1]; + memset(tb, 0x08, sizeof(tb)); // Usage + + tuh_hid_rip_state_t pstate; + tuh_hid_rip_init_state(&pstate, (uint8_t*)&tb, sizeof(tb)); + TEST_ASSERT_EQUAL(pstate.status, HID_RIP_INIT); + for (int i = 0; i < HID_REPORT_MAX_USAGES; ++i) { + TEST_ASSERT_EQUAL(&tb[i], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(pstate.status, HID_RIP_TTEM_OK); + } + TEST_ASSERT_EQUAL(NULL, tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(pstate.status, HID_RIP_USAGES_OVERFLOW); +} + +void test_collection_overflow(void) +{ + uint8_t tb[HID_REPORT_MAX_COLLECTION_DEPTH + 1]; + memset(tb, 0xA0, sizeof(tb)); // Collection start + + tuh_hid_rip_state_t pstate; + tuh_hid_rip_init_state(&pstate, (uint8_t*)&tb, sizeof(tb)); + TEST_ASSERT_EQUAL(pstate.status, HID_RIP_INIT); + for (int i = 0; i < HID_REPORT_MAX_COLLECTION_DEPTH; ++i) { + TEST_ASSERT_EQUAL(&tb[i], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(pstate.status, HID_RIP_TTEM_OK); + } + TEST_ASSERT_EQUAL(NULL, tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(pstate.status, HID_RIP_COLLECTIONS_OVERFLOW); +} + +void test_collection_underflow(void) +{ + uint8_t tb[] = { + 0xA0, // Collection start + 0xC0, // Collection end + 0xC0, // Collection end + }; + + tuh_hid_rip_state_t pstate; + tuh_hid_rip_init_state(&pstate, (uint8_t*)&tb, sizeof(tb)); + TEST_ASSERT_EQUAL(pstate.status, HID_RIP_INIT); + TEST_ASSERT_EQUAL(&tb[0], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(pstate.status, HID_RIP_TTEM_OK); + TEST_ASSERT_EQUAL(&tb[1], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(pstate.status, HID_RIP_TTEM_OK); + TEST_ASSERT_EQUAL(0, tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(pstate.status, HID_RIP_COLLECTIONS_UNDERFLOW); +} + +void test_main_clears_local(void) +{ + uint8_t tb[] = { + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x02, // Usage (Mouse) + 0x39, 0x04, // Designator Index 4 + 0xA1, 0x01, // Collection (Application) + 0x75, 0x05, // REPORT_SIZE (5) + }; + + tuh_hid_rip_state_t pstate; + tuh_hid_rip_init_state(&pstate, (uint8_t*)&tb, sizeof(tb)); + TEST_ASSERT_EQUAL(&tb[0], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(&tb[2], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(&tb[4], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(&tb[6], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(1, pstate.usage_count); + TEST_ASSERT_EQUAL(&tb[4], pstate.local_items[3]); + TEST_ASSERT_EQUAL(4, tuh_hid_ri_short_data32(pstate.local_items[3])); + TEST_ASSERT_EQUAL(&tb[8], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(0, pstate.global_items[pstate.stack_index][3]); + TEST_ASSERT_EQUAL(0, pstate.usage_count); +} + +void test_collections(void) +{ + uint8_t tb[] = { + 0xA1, 0x01, // Collection (Application) + 0xA1, 0x00, // Collection (Physical) + 0xC0, // End Collection + 0xC0, // End Collection + }; + + tuh_hid_rip_state_t pstate; + tuh_hid_rip_init_state(&pstate, (uint8_t*)&tb, sizeof(tb)); + TEST_ASSERT_EQUAL(&tb[0], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(1, pstate.collections_count); + TEST_ASSERT_EQUAL(&tb[2], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(2, pstate.collections_count); + TEST_ASSERT_EQUAL(&tb[0], pstate.collections[0]); + TEST_ASSERT_EQUAL(&tb[2], pstate.collections[1]); + TEST_ASSERT_EQUAL(&tb[4], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(1, pstate.collections_count); + TEST_ASSERT_EQUAL(&tb[5], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(0, pstate.collections_count); +} + +void test_total_size_bits(void) +{ + uint8_t tb[] = { + 0x75, 0x08, // REPORT_SIZE (8) + 0x95, 0x02, // REPORT_COUNT (2) + }; + + tuh_hid_rip_state_t pstate; + tuh_hid_rip_init_state(&pstate, (uint8_t*)&tb, sizeof(tb)); + TEST_ASSERT_EQUAL(&tb[0], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(&tb[2], tuh_hid_rip_next_item(&pstate)); + TEST_ASSERT_EQUAL(16, tuh_hid_rip_report_total_size_bits(&pstate)); +} + + +void test_hid_parse_report_descriptor_single_mouse_report(void) { + const uint8_t const tb[] = { + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x02, // USAGE (Mouse) + 0xa1, 0x01, // COLLECTION (Application) + 0x09, 0x01, // USAGE (Pointer) + 0xa1, 0x00, // COLLECTION (Physical) + 0x05, 0x09, // USAGE_PAGE (Button) + 0x19, 0x01, // USAGE_MINIMUM (Button 1) + 0x29, 0x03, // USAGE_MAXIMUM (Button 3) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x95, 0x03, // REPORT_COUNT (3) + 0x75, 0x01, // REPORT_SIZE (1) + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0x95, 0x01, // REPORT_COUNT (1) + 0x75, 0x05, // REPORT_SIZE (5) + 0x81, 0x03, // INPUT (Cnst,Var,Abs) + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x30, // USAGE (X) + 0x09, 0x31, // USAGE (Y) + 0x15, 0x81, // LOGICAL_MINIMUM (-127) + 0x25, 0x7f, // LOGICAL_MAXIMUM (127) + 0x75, 0x08, // REPORT_SIZE (8) + 0x95, 0x02, // REPORT_COUNT (2) + 0x81, 0x06, // INPUT (Data,Var,Rel) + 0xc0, // END_COLLECTION + 0xc0 // END_COLLECTION + }; + + tuh_hid_report_info_t report_info[3]; + + uint8_t report_count = tuh_hid_parse_report_descriptor(report_info, 3, (const uint8_t*)&tb, sizeof(tb)); + TEST_ASSERT_EQUAL(1, report_count); + TEST_ASSERT_EQUAL(1, report_info[0].usage_page); + TEST_ASSERT_EQUAL(2, report_info[0].usage); + TEST_ASSERT_EQUAL(0, report_info[0].report_id); + TEST_ASSERT_EQUAL(3*1 + 1*5 + 8*2, report_info[0].in_len); + TEST_ASSERT_EQUAL(0, report_info[0].out_len); +} + +void test_hid_parse_report_descriptor_single_gamepad_report(void) { + const uint8_t const tb[] = { + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x05, // USAGE (Game Pad) + 0xa1, 0x01, // COLLECTION (Application) + 0xa1, 0x00, // COLLECTION (Physical) + // ReportID - 8 bits + 0x85, 0x01, // REPORT_ID (1) + // X & Y - 2x8 = 16 bits + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x30, // USAGE (X) + 0x09, 0x31, // USAGE (Y) + 0x15, 0x81, // LOGICAL_MINIMUM (-127) + 0x25, 0x7f, // LOGICAL_MAXIMUM (127) + 0x75, 0x08, // REPORT_SIZE (8) + 0x95, 0x02, // REPORT_COUNT (2) + 0x81, 0x02, // INPUT (Data,Var,Abs) + // Buttons - 8 bits + 0x05, 0x09, // USAGE_PAGE (Button) + 0x19, 0x01, // USAGE_MINIMUM (Button 1) + 0x29, 0x08, // USAGE_MAXIMUM (Button 8) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x75, 0x08, // REPORT_SIZE (8) + 0x95, 0x01, // REPORT_COUNT (1) + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0xc0, // END_COLLECTION + 0xc0 // END_COLLECTION + }; + + tuh_hid_report_info_t report_info[3]; + + uint8_t report_count = tuh_hid_parse_report_descriptor(report_info, 3, (const uint8_t*)&tb, sizeof(tb)); + TEST_ASSERT_EQUAL(1, report_count); + TEST_ASSERT_EQUAL(1, report_info[0].usage_page); + TEST_ASSERT_EQUAL(5, report_info[0].usage); + TEST_ASSERT_EQUAL(1, report_info[0].report_id); + TEST_ASSERT_EQUAL(8*2 + 8*1, report_info[0].in_len); + TEST_ASSERT_EQUAL(0, report_info[0].out_len); +} + +void test_hid_parse_report_descriptor_dual_report(void) { + const uint8_t const tb[] = { + 0x09, 0x01, // Usage (Consumer Control) + 0xA1, 0x01, // Collection (Application) + + 0x85, 0x01, // Report ID (1) + 0x05, 0x0C, // Usage Page (Consumer) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x75, 0x01, // Report Size (1) + 0x95, 0x01, // Report Count (1) + 0x09, 0xE2, // Usage (Mute) + 0x81, 0x62, // Input (Data,Var,Abs,No Wrap,Linear,No Preferred State,Null State) + + 0x85, 0x02, // Report ID (2) + 0x05, 0x0C, // Usage Page (Consumer) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x75, 0x01, // Report Size (1) + 0x95, 0x02, // Report Count (2) + 0x09, 0xE2, // Usage (Mute) + 0x09, 0xE2, // Usage (Mute) + 0x81, 0x62, // Input (Data,Var,Abs,No Wrap,Linear,No Preferred State,Null State) + 0xC0, // End Collection + }; + + tuh_hid_report_info_t report_info[3]; + + uint8_t report_count = tuh_hid_parse_report_descriptor(report_info, 3, (const uint8_t*)&tb, sizeof(tb)); + TEST_ASSERT_EQUAL(2, report_count); + TEST_ASSERT_EQUAL(0, report_info[0].usage_page); + TEST_ASSERT_EQUAL(1, report_info[0].usage); + TEST_ASSERT_EQUAL(1, report_info[0].report_id); + TEST_ASSERT_EQUAL(1*1, report_info[0].in_len); + TEST_ASSERT_EQUAL(0, report_info[0].out_len); + TEST_ASSERT_EQUAL(0, report_info[1].usage_page); + TEST_ASSERT_EQUAL(1, report_info[1].usage); + TEST_ASSERT_EQUAL(2, report_info[1].report_id); + TEST_ASSERT_EQUAL(1*2, report_info[1].in_len); + TEST_ASSERT_EQUAL(0, report_info[1].out_len); +} + +void test_hid_parse_report_descriptor_joystick_report(void) { + const uint8_t const tb[] = { + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x04, // Usage (Joystick) + 0xA1, 0x01, // Collection (Application) + 0x09, 0x01, // Usage (Pointer) + 0xA1, 0x00, // Collection (Physical) + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x30, // Usage (X) + 0x09, 0x31, // Usage (Y) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x03, // Logical Maximum (1023) + 0x35, 0x00, // Physical Minimum (0) + 0x46, 0xFF, 0x03, // Physical Maximum (1023) + 0x65, 0x00, // Unit (None) + 0x75, 0x0A, // Report Size (10) + 0x95, 0x02, // Report Count (2) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x09, 0x35, // Usage (Rz) + 0x09, 0x32, // Usage (Z) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x01, // Logical Maximum (511) + 0x35, 0x00, // Physical Minimum (0) + 0x46, 0xFF, 0x01, // Physical Maximum (511) + 0x65, 0x00, // Unit (None) + 0x75, 0x09, // Report Size (9) + 0x95, 0x02, // Report Count (2) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x75, 0x01, // Report Size (1) + 0x95, 0x02, // Report Count (2) + 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x09, 0x39, // Usage (Hat switch) + 0x15, 0x01, // Logical Minimum (1) + 0x25, 0x08, // Logical Maximum (8) + 0x35, 0x00, // Physical Minimum (0) + 0x46, 0x3B, 0x01, // Physical Maximum (315) + 0x65, 0x14, // Unit (System: English Rotation, Length: Centimeter) + 0x75, 0x08, // Report Size (8) + 0x95, 0x01, // Report Count (1) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x05, 0x09, // Usage Page (Button) + 0x19, 0x01, // Usage Minimum (0x01) + 0x29, 0x0C, // Usage Maximum (0x0C) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x35, 0x00, // Physical Minimum (0) + 0x45, 0x01, // Physical Maximum (1) + 0x75, 0x01, // Report Size (1) + 0x95, 0x0C, // Report Count (12) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x75, 0x01, // Report Size (1) + 0x95, 0x04, // Report Count (4) + 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0xC0, // End Collection + 0xC0, // End Collection + }; + tuh_hid_report_info_t report_info[3]; + + uint8_t report_count = tuh_hid_parse_report_descriptor(report_info, 3, (const uint8_t*)&tb, sizeof(tb)); + TEST_ASSERT_EQUAL(1, report_count); + TEST_ASSERT_EQUAL(1, report_info[0].usage_page); + TEST_ASSERT_EQUAL(4, report_info[0].usage); + TEST_ASSERT_EQUAL(0, report_info[0].report_id); + TEST_ASSERT_EQUAL(64, report_info[0].in_len); + TEST_ASSERT_EQUAL(0, report_info[0].out_len); +} + +void test_hid_parse_greenasia_report(void) { + const uint8_t const tb[] = { + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x04, // Usage (Joystick) + 0xA1, 0x01, // Collection (Application) + 0xA1, 0x02, // Collection (Logical) + 0x75, 0x08, // Report Size (8) + 0x95, 0x05, // Report Count (5) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x35, 0x00, // Physical Minimum (0) + 0x46, 0xFF, 0x00, // Physical Maximum (255) + 0x09, 0x32, // Usage (Z) + 0x09, 0x35, // Usage (Rz) + 0x09, 0x30, // Usage (X) + 0x09, 0x31, // Usage (Y) + 0x09, 0x00, // Usage (Undefined) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x75, 0x04, // Report Size (4) + 0x95, 0x01, // Report Count (1) + 0x25, 0x07, // Logical Maximum (7) + 0x46, 0x3B, 0x01, // Physical Maximum (315) + 0x65, 0x14, // Unit (System: English Rotation, Length: Centimeter) + 0x09, 0x39, // Usage (Hat switch) + 0x81, 0x42, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State) + 0x65, 0x00, // Unit (None) + 0x75, 0x01, // Report Size (1) + 0x95, 0x0C, // Report Count (12) + 0x25, 0x01, // Logical Maximum (1) + 0x45, 0x01, // Physical Maximum (1) + 0x05, 0x09, // Usage Page (Button) + 0x19, 0x01, // Usage Minimum (0x01) + 0x29, 0x0C, // Usage Maximum (0x0C) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00) + 0x75, 0x01, // Report Size (1) + 0x95, 0x08, // Report Count (8) + 0x25, 0x01, // Logical Maximum (1) + 0x45, 0x01, // Physical Maximum (1) + 0x09, 0x01, // Usage (0x01) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0xC0, // End Collection + 0xA1, 0x02, // Collection (Logical) + 0x75, 0x08, // Report Size (8) + 0x95, 0x04, // Report Count (4) + 0x46, 0xFF, 0x00, // Physical Maximum (255) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x09, 0x02, // Usage (0x02) + 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0xC0, // End Collection + 0xC0, // End Collection + }; + tuh_hid_report_info_t report_info[3]; + + uint8_t report_count = tuh_hid_parse_report_descriptor(report_info, 3, (const uint8_t*)&tb, sizeof(tb)); + TEST_ASSERT_EQUAL(1, report_count); +} + +void test_hid_parse_speedlink_report(void) { + const uint8_t const tb[] = { + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x04, // Usage (Joystick) + 0xA1, 0x01, // Collection (Application) + 0xA1, 0x02, // Collection (Logical) + 0x75, 0x08, // Report Size (8) + 0x95, 0x05, // Report Count (5) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x35, 0x00, // Physical Minimum (0) + 0x46, 0xFF, 0x00, // Physical Maximum (255) + 0x09, 0x30, // Usage (X) + 0x09, 0x31, // Usage (Y) + 0x09, 0x32, // Usage (Z) + 0x09, 0x32, // Usage (Z) + 0x09, 0x35, // Usage (Rz) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x75, 0x04, // Report Size (4) + 0x95, 0x01, // Report Count (1) + 0x25, 0x07, // Logical Maximum (7) + 0x46, 0x3B, 0x01, // Physical Maximum (315) + 0x65, 0x14, // Unit (System: English Rotation, Length: Centimeter) + 0x09, 0x39, // Usage (Hat switch) + 0x81, 0x42, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State) + 0x65, 0x00, // Unit (None) + 0x75, 0x01, // Report Size (1) + 0x95, 0x0C, // Report Count (12) + 0x25, 0x01, // Logical Maximum (1) + 0x45, 0x01, // Physical Maximum (1) + 0x05, 0x09, // Usage Page (Button) + 0x19, 0x01, // Usage Minimum (0x01) + 0x29, 0x0C, // Usage Maximum (0x0C) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00) + 0x75, 0x01, // Report Size (1) + 0x95, 0x08, // Report Count (8) + 0x25, 0x01, // Logical Maximum (1) + 0x45, 0x01, // Physical Maximum (1) + 0x09, 0x01, // Usage (0x01) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0xC0, // End Collection + 0xA1, 0x02, // Collection (Logical) + 0x75, 0x08, // Report Size (8) + 0x95, 0x07, // Report Count (7) + 0x46, 0xFF, 0x00, // Physical Maximum (255) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x09, 0x02, // Usage (0x02) + 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0xC0, // End Collection + 0xC0, // End Collection + }; + tuh_hid_report_info_t report_info[3]; + + uint8_t report_count = tuh_hid_parse_report_descriptor(report_info, 3, (const uint8_t*)&tb, sizeof(tb)); + TEST_ASSERT_EQUAL(1, report_count); +} + +void test_hid_parse_keyboard_and_trackpad_report(void) { + const uint8_t const tb[] = { + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x02, // Usage (Mouse) + 0xA1, 0x01, // Collection (Application) + 0x85, 0x02, // Report ID (2) + 0x09, 0x01, // Usage (Pointer) + 0xA1, 0x00, // Collection (Physical) + 0x05, 0x09, // Usage Page (Button) + 0x19, 0x01, // Usage Minimum (0x01) + 0x29, 0x10, // Usage Maximum (0x10) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x95, 0x10, // Report Count (16) + 0x75, 0x01, // Report Size (1) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x16, 0x01, 0xF8, // Logical Minimum (-2047) + 0x26, 0xFF, 0x07, // Logical Maximum (2047) + 0x75, 0x0C, // Report Size (12) + 0x95, 0x02, // Report Count (2) + 0x09, 0x30, // Usage (X) + 0x09, 0x31, // Usage (Y) + 0x81, 0x06, // Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position) + 0x15, 0x81, // Logical Minimum (-127) + 0x25, 0x7F, // Logical Maximum (127) + 0x75, 0x08, // Report Size (8) + 0x95, 0x01, // Report Count (1) + 0x09, 0x38, // Usage (Wheel) + 0x81, 0x06, // Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position) + 0x05, 0x0C, // Usage Page (Consumer) + 0x0A, 0x38, 0x02, // Usage (AC Pan) + 0x95, 0x01, // Report Count (1) + 0x81, 0x06, // Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position) + 0xC0, // End Collection + 0xC0, // End Collection + 0x05, 0x0C, // Usage Page (Consumer) + 0x09, 0x01, // Usage (Consumer Control) + 0xA1, 0x01, // Collection (Application) + 0x85, 0x03, // Report ID (3) + 0x75, 0x10, // Report Size (16) + 0x95, 0x02, // Report Count (2) + 0x15, 0x01, // Logical Minimum (1) + 0x26, 0x8C, 0x02, // Logical Maximum (652) + 0x19, 0x01, // Usage Minimum (Consumer Control) + 0x2A, 0x8C, 0x02, // Usage Maximum (AC Send) + 0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0xC0, // End Collection + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x80, // Usage (Sys Control) + 0xA1, 0x01, // Collection (Application) + 0x85, 0x04, // Report ID (4) + 0x75, 0x02, // Report Size (2) + 0x95, 0x01, // Report Count (1) + 0x15, 0x01, // Logical Minimum (1) + 0x25, 0x03, // Logical Maximum (3) + 0x09, 0x82, // Usage (Sys Sleep) + 0x09, 0x81, // Usage (Sys Power Down) + 0x09, 0x83, // Usage (Sys Wake Up) + 0x81, 0x60, // Input (Data,Array,Abs,No Wrap,Linear,No Preferred State,Null State) + 0x75, 0x06, // Report Size (6) + 0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0xC0, // End Collection + 0x06, 0xBC, 0xFF, // Usage Page (Vendor Defined 0xFFBC) + 0x09, 0x88, // Usage (0x88) + 0xA1, 0x01, // Collection (Application) + 0x85, 0x08, // Report ID (8) + 0x19, 0x01, // Usage Minimum (0x01) + 0x29, 0xFF, // Usage Maximum (0xFF) + 0x15, 0x01, // Logical Minimum (1) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8) + 0x95, 0x01, // Report Count (1) + 0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0xC0, // End Collection + }; + tuh_hid_report_info_t report_info[5]; + + uint8_t report_count; + report_count = tuh_hid_parse_report_descriptor(report_info, 1, (const uint8_t*)&tb, sizeof(tb)); + TEST_ASSERT_EQUAL(1, report_count); + report_count = tuh_hid_parse_report_descriptor(report_info, 2, (const uint8_t*)&tb, sizeof(tb)); + TEST_ASSERT_EQUAL(2, report_count); + report_count = tuh_hid_parse_report_descriptor(report_info, 3, (const uint8_t*)&tb, sizeof(tb)); + TEST_ASSERT_EQUAL(3, report_count); + report_count = tuh_hid_parse_report_descriptor(report_info, 4, (const uint8_t*)&tb, sizeof(tb)); + TEST_ASSERT_EQUAL(4, report_count); + report_count = tuh_hid_parse_report_descriptor(report_info, 5, (const uint8_t*)&tb, sizeof(tb)); + TEST_ASSERT_EQUAL(4, report_count); + + TEST_ASSERT_EQUAL(1, report_info[0].usage_page); + TEST_ASSERT_EQUAL(2, report_info[0].usage); + TEST_ASSERT_EQUAL(2, report_info[0].report_id); + TEST_ASSERT_EQUAL(56, report_info[0].in_len); + TEST_ASSERT_EQUAL(0, report_info[0].out_len); + + TEST_ASSERT_EQUAL(0xC, report_info[1].usage_page); + TEST_ASSERT_EQUAL(0x1, report_info[1].usage); + TEST_ASSERT_EQUAL(3, report_info[1].report_id); + TEST_ASSERT_EQUAL(32, report_info[1].in_len); + TEST_ASSERT_EQUAL(0, report_info[1].out_len); + + TEST_ASSERT_EQUAL(1, report_info[2].usage_page); + TEST_ASSERT_EQUAL(0x80, report_info[2].usage); + TEST_ASSERT_EQUAL(4, report_info[2].report_id); + TEST_ASSERT_EQUAL(8, report_info[2].in_len); + TEST_ASSERT_EQUAL(0, report_info[2].out_len); + + TEST_ASSERT_EQUAL(0xFFBC, report_info[3].usage_page); + TEST_ASSERT_EQUAL(0x88, report_info[3].usage); + TEST_ASSERT_EQUAL(8, report_info[3].report_id); + TEST_ASSERT_EQUAL(8, report_info[3].in_len); + TEST_ASSERT_EQUAL(0, report_info[3].out_len); +} + + + + + + diff --git a/test/test/examples/host/cdc_msc_hid/test_hid_host_joy.c b/test/test/examples/host/cdc_msc_hid/test_hid_host_joy.c new file mode 100644 index 0000000000..7fab2f1155 --- /dev/null +++ b/test/test/examples/host/cdc_msc_hid/test_hid_host_joy.c @@ -0,0 +1,520 @@ +/* + * The MIT License (MIT) + * + * 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. + */ + + +#include "unity.h" + +// Files to test +#include "hid_rip.h" +#include "hid_host_joy.h" +TEST_FILE("hid_ri.c") +TEST_FILE("hid_rip.c") +TEST_FILE("hid_host_utils.c") +TEST_FILE("hid_host_joy.c") + +static const uint8_t const tb_greenasia[] = { + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x04, // Usage (Joystick) + 0xA1, 0x01, // Collection (Application) + 0xA1, 0x02, // Collection (Logical) + 0x75, 0x08, // Report Size (8) + 0x95, 0x05, // Report Count (5) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x35, 0x00, // Physical Minimum (0) + 0x46, 0xFF, 0x00, // Physical Maximum (255) + 0x09, 0x32, // Usage (Z) + 0x09, 0x35, // Usage (Rz) + 0x09, 0x30, // Usage (X) + 0x09, 0x31, // Usage (Y) + 0x09, 0x00, // Usage (Undefined) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x75, 0x04, // Report Size (4) + 0x95, 0x01, // Report Count (1) + 0x25, 0x07, // Logical Maximum (7) + 0x46, 0x3B, 0x01, // Physical Maximum (315) + 0x65, 0x14, // Unit (System: English Rotation, Length: Centimeter) + 0x09, 0x39, // Usage (Hat switch) + 0x81, 0x42, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State) + 0x65, 0x00, // Unit (None) + 0x75, 0x01, // Report Size (1) + 0x95, 0x0C, // Report Count (12) + 0x25, 0x01, // Logical Maximum (1) + 0x45, 0x01, // Physical Maximum (1) + 0x05, 0x09, // Usage Page (Button) + 0x19, 0x01, // Usage Minimum (0x01) + 0x29, 0x0C, // Usage Maximum (0x0C) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00) + 0x75, 0x01, // Report Size (1) + 0x95, 0x08, // Report Count (8) + 0x25, 0x01, // Logical Maximum (1) + 0x45, 0x01, // Physical Maximum (1) + 0x09, 0x01, // Usage (0x01) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0xC0, // End Collection + 0xA1, 0x02, // Collection (Logical) + 0x75, 0x08, // Report Size (8) + 0x95, 0x04, // Report Count (4) + 0x46, 0xFF, 0x00, // Physical Maximum (255) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x09, 0x02, // Usage (0x02) + 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0xC0, // End Collection + 0xC0, // End Collection +}; + +static const uint8_t const tb_speedlink[] = { + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x04, // Usage (Joystick) + 0xA1, 0x01, // Collection (Application) + 0xA1, 0x02, // Collection (Logical) + 0x75, 0x08, // Report Size (8) + 0x95, 0x05, // Report Count (5) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x35, 0x00, // Physical Minimum (0) + 0x46, 0xFF, 0x00, // Physical Maximum (255) + 0x09, 0x30, // Usage (X) + 0x09, 0x31, // Usage (Y) + 0x09, 0x32, // Usage (Z) + 0x09, 0x32, // Usage (Z) + 0x09, 0x35, // Usage (Rz) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x75, 0x04, // Report Size (4) + 0x95, 0x01, // Report Count (1) + 0x25, 0x07, // Logical Maximum (7) + 0x46, 0x3B, 0x01, // Physical Maximum (315) + 0x65, 0x14, // Unit (System: English Rotation, Length: Centimeter) + 0x09, 0x39, // Usage (Hat switch) + 0x81, 0x42, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State) + 0x65, 0x00, // Unit (None) + 0x75, 0x01, // Report Size (1) + 0x95, 0x0C, // Report Count (12) + 0x25, 0x01, // Logical Maximum (1) + 0x45, 0x01, // Physical Maximum (1) + 0x05, 0x09, // Usage Page (Button) + 0x19, 0x01, // Usage Minimum (0x01) + 0x29, 0x0C, // Usage Maximum (0x0C) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00) + 0x75, 0x01, // Report Size (1) + 0x95, 0x08, // Report Count (8) + 0x25, 0x01, // Logical Maximum (1) + 0x45, 0x01, // Physical Maximum (1) + 0x09, 0x01, // Usage (0x01) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0xC0, // End Collection + 0xA1, 0x02, // Collection (Logical) + 0x75, 0x08, // Report Size (8) + 0x95, 0x07, // Report Count (7) + 0x46, 0xFF, 0x00, // Physical Maximum (255) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x09, 0x02, // Usage (0x02) + 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0xC0, // End Collection + 0xC0, // End Collection + }; + +static const uint8_t const tb_apple[] = { + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x04, // Usage (Joystick) + 0xA1, 0x01, // Collection (Application) + 0x09, 0x01, // Usage (Pointer) + 0xA1, 0x00, // Collection (Physical) + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x30, // Usage (X) + 0x09, 0x31, // Usage (Y) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x03, // Logical Maximum (1023) + 0x35, 0x00, // Physical Minimum (0) + 0x46, 0xFF, 0x03, // Physical Maximum (1023) + 0x65, 0x00, // Unit (None) + 0x75, 0x0A, // Report Size (10) + 0x95, 0x02, // Report Count (2) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x09, 0x35, // Usage (Rz) + 0x09, 0x32, // Usage (Z) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x01, // Logical Maximum (511) + 0x35, 0x00, // Physical Minimum (0) + 0x46, 0xFF, 0x01, // Physical Maximum (511) + 0x65, 0x00, // Unit (None) + 0x75, 0x09, // Report Size (9) + 0x95, 0x02, // Report Count (2) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x75, 0x01, // Report Size (1) + 0x95, 0x02, // Report Count (2) + 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x09, 0x39, // Usage (Hat switch) + 0x15, 0x01, // Logical Minimum (1) + 0x25, 0x08, // Logical Maximum (8) + 0x35, 0x00, // Physical Minimum (0) + 0x46, 0x3B, 0x01, // Physical Maximum (315) + 0x65, 0x14, // Unit (System: English Rotation, Length: Centimeter) + 0x75, 0x08, // Report Size (8) + 0x95, 0x01, // Report Count (1) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x05, 0x09, // Usage Page (Button) + 0x19, 0x01, // Usage Minimum (0x01) + 0x29, 0x0C, // Usage Maximum (0x0C) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x35, 0x00, // Physical Minimum (0) + 0x45, 0x01, // Physical Maximum (1) + 0x75, 0x01, // Report Size (1) + 0x95, 0x0C, // Report Count (12) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x75, 0x01, // Report Size (1) + 0x95, 0x04, // Report Count (4) + 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0xC0, // End Collection + 0xC0, // End Collection +}; + + +void setUp(void) +{ + tuh_hid_free_simple_joysticks(); +} + +void tearDown(void) +{ +} + +//--------------------------------------------------------------------+ +// Tests +//--------------------------------------------------------------------+ + +void test_simple_joystick_allocator(void) { + TEST_ASSERT_NULL(tuh_hid_get_simple_joystick(5, 1, 2)); + TEST_ASSERT_NULL(tuh_hid_get_simple_joystick(5, 1, 3)); + TEST_ASSERT_NULL(tuh_hid_get_simple_joystick(4, 2, 3)); + TEST_ASSERT_NULL(tuh_hid_get_simple_joystick(4, 5, 2)); + TEST_ASSERT_NOT_NULL(tuh_hid_allocate_simple_joystick(5, 1, 2)); + TEST_ASSERT_NOT_NULL(tuh_hid_allocate_simple_joystick(5, 1, 3)); + TEST_ASSERT_NOT_NULL(tuh_hid_allocate_simple_joystick(4, 2, 3)); + TEST_ASSERT_NOT_NULL(tuh_hid_allocate_simple_joystick(4, 5, 2)); + TEST_ASSERT_NOT_NULL(tuh_hid_get_simple_joystick(5, 1, 2)); + TEST_ASSERT_NOT_NULL(tuh_hid_get_simple_joystick(5, 1, 3)); + TEST_ASSERT_NOT_NULL(tuh_hid_get_simple_joystick(4, 2, 3)); + TEST_ASSERT_NOT_NULL(tuh_hid_get_simple_joystick(4, 5, 2)); + tuh_hid_free_simple_joysticks_for_instance(5, 1); + TEST_ASSERT_NULL(tuh_hid_get_simple_joystick(5, 1, 2)); + TEST_ASSERT_NULL(tuh_hid_get_simple_joystick(5, 1, 3)); + TEST_ASSERT_NOT_NULL(tuh_hid_get_simple_joystick(4, 2, 3)); + TEST_ASSERT_NOT_NULL(tuh_hid_get_simple_joystick(4, 5, 2)); + tuh_hid_free_simple_joysticks_for_instance(4, 5); + TEST_ASSERT_NULL(tuh_hid_get_simple_joystick(4, 5, 2)); + TEST_ASSERT_NOT_NULL(tuh_hid_get_simple_joystick(4, 2, 3)); +} + +void test_simple_joystick_allocate_too_many(void) { + for (int i =0; i < HID_MAX_JOYSTICKS; ++i) { + TEST_ASSERT_NOT_NULL(tuh_hid_allocate_simple_joystick(1, i + 1, 4)); + } + TEST_ASSERT_NULL(tuh_hid_allocate_simple_joystick(2, 1, 0)); +} + +void test_simple_joystick_free_all(void) { + TEST_ASSERT_NOT_NULL(tuh_hid_allocate_simple_joystick(7, 1, 3)); + TEST_ASSERT_NOT_NULL(tuh_hid_allocate_simple_joystick(0, 2, 3)); + tuh_hid_free_simple_joysticks(); + TEST_ASSERT_NULL(tuh_hid_get_simple_joystick(7, 1, 3)); + TEST_ASSERT_NULL(tuh_hid_get_simple_joystick(0, 2, 3)); +} + +void test_simple_joystick_obtain(void) { + tusb_hid_simple_joysick_t* j1 = tuh_hid_allocate_simple_joystick(7, 1, 3); + TEST_ASSERT_NOT_NULL(j1); + TEST_ASSERT_EQUAL(j1, tuh_hid_obtain_simple_joystick(7, 1, 3)); + TEST_ASSERT_NULL(tuh_hid_get_simple_joystick(7, 2, 3)); + tusb_hid_simple_joysick_t* j2 = tuh_hid_allocate_simple_joystick(7, 2, 3); + TEST_ASSERT_NOT_NULL(j2); + TEST_ASSERT_NOT_EQUAL(j1,j2); +} + +void test_tuh_hid_joystick_get_data(void) { + tuh_hid_joystick_data_t joystick_data; + tuh_hid_rip_state_t pstate; + tusb_hid_simple_joysick_t* simple_joystick; + tuh_hid_rip_init_state(&pstate, tb_speedlink, sizeof(tb_speedlink)); + const uint8_t *ri; + + while((ri = tuh_hid_rip_next_item(&pstate)) != NULL ) if (ri >= &tb_speedlink[32]) break; + TEST_ASSERT_EQUAL(&tb_speedlink[32], ri); // Move to the first input in the speedlink description + TEST_ASSERT_EQUAL(true, tuh_hid_joystick_get_data(&pstate, ri, &joystick_data)); + + TEST_ASSERT_EQUAL(8, joystick_data.report_size); + TEST_ASSERT_EQUAL(5, joystick_data.report_count); + TEST_ASSERT_EQUAL(0, joystick_data.report_id); + TEST_ASSERT_EQUAL(0, joystick_data.logical_min); + TEST_ASSERT_EQUAL(255, joystick_data.logical_max); + TEST_ASSERT_EQUAL(false, joystick_data.usage_is_range); + + // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + TEST_ASSERT_EQUAL(false, joystick_data.input_flags.data_const); + TEST_ASSERT_EQUAL(true, joystick_data.input_flags.array_variable); + TEST_ASSERT_EQUAL(false, joystick_data.input_flags.absolute_relative); + TEST_ASSERT_EQUAL(false, joystick_data.input_flags.nowrap_wrap); + TEST_ASSERT_EQUAL(false, joystick_data.input_flags.linear_nonlinear); + TEST_ASSERT_EQUAL(false, joystick_data.input_flags.prefered_noprefered); + TEST_ASSERT_EQUAL(false, joystick_data.input_flags.nonull_null); + + tuh_hid_joystick_process_usages(&pstate, &joystick_data, 0, 5, 9); + simple_joystick = tuh_hid_get_simple_joystick(5, 9, 0); + TEST_ASSERT_NOT_NULL(simple_joystick); + // x1 + TEST_ASSERT_EQUAL(0, simple_joystick->axis_x1.start); + TEST_ASSERT_EQUAL(8, simple_joystick->axis_x1.length); + TEST_ASSERT_EQUAL(false, simple_joystick->axis_x1.flags.is_signed); + TEST_ASSERT_EQUAL(0, simple_joystick->axis_x1.logical_min); + TEST_ASSERT_EQUAL(255, simple_joystick->axis_x1.logical_max); + + // y1 + TEST_ASSERT_EQUAL(8, simple_joystick->axis_y1.start); + TEST_ASSERT_EQUAL(8, simple_joystick->axis_y1.length); + TEST_ASSERT_EQUAL(false, simple_joystick->axis_y1.flags.is_signed); + TEST_ASSERT_EQUAL(0, simple_joystick->axis_y1.logical_min); + TEST_ASSERT_EQUAL(255, simple_joystick->axis_y1.logical_max); + // x2 + TEST_ASSERT_EQUAL(24, simple_joystick->axis_x2.start); + TEST_ASSERT_EQUAL(8, simple_joystick->axis_x2.length); + TEST_ASSERT_EQUAL(false, simple_joystick->axis_x2.flags.is_signed); + TEST_ASSERT_EQUAL(0, simple_joystick->axis_x2.logical_min); + TEST_ASSERT_EQUAL(255, simple_joystick->axis_x2.logical_max); + // y2 + TEST_ASSERT_EQUAL(32, simple_joystick->axis_y2.start); + TEST_ASSERT_EQUAL(8, simple_joystick->axis_y2.length); + TEST_ASSERT_EQUAL(false, simple_joystick->axis_y2.flags.is_signed); + TEST_ASSERT_EQUAL(0, simple_joystick->axis_y2.logical_min); + TEST_ASSERT_EQUAL(255, simple_joystick->axis_y2.logical_max); + + while((ri = tuh_hid_rip_next_item(&pstate)) != NULL ) if (ri >= &tb_speedlink[47]) break; + TEST_ASSERT_EQUAL(&tb_speedlink[47], ri); // Move to the second input in the speedlink description + TEST_ASSERT_EQUAL(true, tuh_hid_joystick_get_data(&pstate, ri, &joystick_data)); + + TEST_ASSERT_EQUAL(4, joystick_data.report_size); + TEST_ASSERT_EQUAL(1, joystick_data.report_count); + TEST_ASSERT_EQUAL(0, joystick_data.report_id); + TEST_ASSERT_EQUAL(0, joystick_data.logical_min); + TEST_ASSERT_EQUAL(7, joystick_data.logical_max); + TEST_ASSERT_EQUAL(false, joystick_data.usage_is_range); + + // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State) + TEST_ASSERT_EQUAL(false, joystick_data.input_flags.data_const); + TEST_ASSERT_EQUAL(true, joystick_data.input_flags.array_variable); + TEST_ASSERT_EQUAL(false, joystick_data.input_flags.absolute_relative); + TEST_ASSERT_EQUAL(false, joystick_data.input_flags.nowrap_wrap); + TEST_ASSERT_EQUAL(false, joystick_data.input_flags.linear_nonlinear); + TEST_ASSERT_EQUAL(false, joystick_data.input_flags.prefered_noprefered); + TEST_ASSERT_EQUAL(true, joystick_data.input_flags.nonull_null); + + tuh_hid_joystick_process_usages(&pstate, &joystick_data, 40, 5, 9); + simple_joystick = tuh_hid_get_simple_joystick(5, 9, 0); + TEST_ASSERT_NOT_NULL(simple_joystick); + TEST_ASSERT_EQUAL(40, simple_joystick->hat.start); + TEST_ASSERT_EQUAL(4, simple_joystick->hat.length); + + while((ri = tuh_hid_rip_next_item(&pstate)) != NULL ) if (ri >= &tb_speedlink[65]) break; + TEST_ASSERT_EQUAL(&tb_speedlink[65], ri); // Move to the second input in the speedlink description + TEST_ASSERT_EQUAL(true, tuh_hid_joystick_get_data(&pstate, ri, &joystick_data)); + + TEST_ASSERT_EQUAL(1, joystick_data.report_size); + TEST_ASSERT_EQUAL(12, joystick_data.report_count); + TEST_ASSERT_EQUAL(0, joystick_data.report_id); + TEST_ASSERT_EQUAL(0, joystick_data.logical_min); + TEST_ASSERT_EQUAL(1, joystick_data.logical_max); + TEST_ASSERT_EQUAL(true, joystick_data.usage_is_range); + TEST_ASSERT_EQUAL(1, joystick_data.usage_min); + TEST_ASSERT_EQUAL(12, joystick_data.usage_max); + + // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + TEST_ASSERT_EQUAL(false, joystick_data.input_flags.data_const); + TEST_ASSERT_EQUAL(true, joystick_data.input_flags.array_variable); + TEST_ASSERT_EQUAL(false, joystick_data.input_flags.absolute_relative); + TEST_ASSERT_EQUAL(false, joystick_data.input_flags.nowrap_wrap); + TEST_ASSERT_EQUAL(false, joystick_data.input_flags.linear_nonlinear); + TEST_ASSERT_EQUAL(false, joystick_data.input_flags.prefered_noprefered); + TEST_ASSERT_EQUAL(false, joystick_data.input_flags.nonull_null); + + tuh_hid_joystick_process_usages(&pstate, &joystick_data, 44, 5, 9); // 56 + simple_joystick = tuh_hid_get_simple_joystick(5, 9, 0); + TEST_ASSERT_NOT_NULL(simple_joystick); + TEST_ASSERT_EQUAL(44, simple_joystick->buttons.start); + TEST_ASSERT_EQUAL(12, simple_joystick->buttons.length); + + TEST_ASSERT_EQUAL(7, simple_joystick->report_length); +} + +void test_hid_parse_greenasia_report(void) { + tuh_hid_joystick_parse_report_descriptor(tb_speedlink, sizeof(tb_speedlink), 5, 9); + tusb_hid_simple_joysick_t* simple_joystick = tuh_hid_get_simple_joystick(5, 9, 0); + TEST_ASSERT_NOT_NULL(simple_joystick); + // x1 + TEST_ASSERT_EQUAL(0, simple_joystick->axis_x1.start); + TEST_ASSERT_EQUAL(8, simple_joystick->axis_x1.length); + TEST_ASSERT_EQUAL(false, simple_joystick->axis_x1.flags.is_signed); + // y1 + TEST_ASSERT_EQUAL(8, simple_joystick->axis_y1.start); + TEST_ASSERT_EQUAL(8, simple_joystick->axis_y1.length); + TEST_ASSERT_EQUAL(false, simple_joystick->axis_y1.flags.is_signed); + // x2 + TEST_ASSERT_EQUAL(24, simple_joystick->axis_x2.start); + TEST_ASSERT_EQUAL(8, simple_joystick->axis_x2.length); + TEST_ASSERT_EQUAL(false, simple_joystick->axis_x2.flags.is_signed); + // y2 + TEST_ASSERT_EQUAL(32, simple_joystick->axis_y2.start); + TEST_ASSERT_EQUAL(8, simple_joystick->axis_y2.length); + TEST_ASSERT_EQUAL(false, simple_joystick->axis_y2.flags.is_signed); + TEST_ASSERT_EQUAL(40, simple_joystick->hat.start); + TEST_ASSERT_EQUAL(4, simple_joystick->hat.length); + TEST_ASSERT_EQUAL(44, simple_joystick->buttons.start); + TEST_ASSERT_EQUAL(12, simple_joystick->buttons.length); + + TEST_ASSERT_EQUAL(8, simple_joystick->report_length); +} + +void test_hid_parse_speedlink_report(void) { + tuh_hid_joystick_parse_report_descriptor(tb_greenasia, sizeof(tb_greenasia), 5, 9); + tusb_hid_simple_joysick_t* simple_joystick = tuh_hid_get_simple_joystick(5, 9, 0); + TEST_ASSERT_NOT_NULL(simple_joystick); + // x1 + TEST_ASSERT_EQUAL(16, simple_joystick->axis_x1.start); + TEST_ASSERT_EQUAL(8, simple_joystick->axis_x1.length); + TEST_ASSERT_EQUAL(false, simple_joystick->axis_x1.flags.is_signed); + // y1 + TEST_ASSERT_EQUAL(24, simple_joystick->axis_y1.start); + TEST_ASSERT_EQUAL(8, simple_joystick->axis_y1.length); + TEST_ASSERT_EQUAL(false, simple_joystick->axis_y1.flags.is_signed); + // x2 + TEST_ASSERT_EQUAL(0, simple_joystick->axis_x2.start); + TEST_ASSERT_EQUAL(8, simple_joystick->axis_x2.length); + TEST_ASSERT_EQUAL(false, simple_joystick->axis_x2.flags.is_signed); + // y2 + TEST_ASSERT_EQUAL(8, simple_joystick->axis_y2.start); + TEST_ASSERT_EQUAL(8, simple_joystick->axis_y2.length); + TEST_ASSERT_EQUAL(false, simple_joystick->axis_y2.flags.is_signed); + + TEST_ASSERT_EQUAL(40, simple_joystick->hat.start); + TEST_ASSERT_EQUAL(4, simple_joystick->hat.length); + TEST_ASSERT_EQUAL(44, simple_joystick->buttons.start); + TEST_ASSERT_EQUAL(12, simple_joystick->buttons.length); + + TEST_ASSERT_EQUAL(8, simple_joystick->report_length); +} + +void test_apple_joystick(void) { + // Thanks to https://jenswilly.dk/2012/10/parsing-usb-joystick-hid-data/ + + // 10 bits of X-axis data (for values from 0 to 1023) + // 10 bits of Y-axis data (for values from 0 to 1023) + // 9 bits of Rz-axis (yaw or rotation) data (for values from 0-511) + // 9 bits of Z-axis (throttle) data (for values from 0-511) + // 2 "constant" bits – i.e. unused padding bits + // 8 bits of hat switch data (though values are only from 1-8) + // 12 bits of button states (12 buttons of 0 or 1 values) + // 4 bits of padding + tuh_hid_joystick_parse_report_descriptor(tb_apple, sizeof(tb_apple), 5, 9); + tusb_hid_simple_joysick_t* simple_joystick = tuh_hid_get_simple_joystick(5, 9, 0); + TEST_ASSERT_NOT_NULL(simple_joystick); + // x1 + TEST_ASSERT_EQUAL(0, simple_joystick->axis_x1.start); + TEST_ASSERT_EQUAL(10, simple_joystick->axis_x1.length); + TEST_ASSERT_EQUAL(false, simple_joystick->axis_x1.flags.is_signed); + TEST_ASSERT_EQUAL(0, simple_joystick->axis_x1.logical_min); + TEST_ASSERT_EQUAL(1023, simple_joystick->axis_x1.logical_max); + // y1 + TEST_ASSERT_EQUAL(10, simple_joystick->axis_y1.start); + TEST_ASSERT_EQUAL(10, simple_joystick->axis_y1.length); + TEST_ASSERT_EQUAL(false, simple_joystick->axis_y1.flags.is_signed); + // x2 + TEST_ASSERT_EQUAL(29, simple_joystick->axis_x2.start); + TEST_ASSERT_EQUAL(9, simple_joystick->axis_x2.length); + TEST_ASSERT_EQUAL(false, simple_joystick->axis_x2.flags.is_signed); + // y2 + TEST_ASSERT_EQUAL(20, simple_joystick->axis_y2.start); + TEST_ASSERT_EQUAL(9, simple_joystick->axis_y2.length); + TEST_ASSERT_EQUAL(false, simple_joystick->axis_y2.flags.is_signed); + + TEST_ASSERT_EQUAL(40, simple_joystick->hat.start); + TEST_ASSERT_EQUAL(8, simple_joystick->hat.length); + + TEST_ASSERT_EQUAL(48, simple_joystick->buttons.start); + TEST_ASSERT_EQUAL(12, simple_joystick->buttons.length); + + // Raw hex: e9 31 e8 ef 3f 00 00 00 + // + // Binary 11101001 00110001 11101000 11101111 00111111 00000000 00000000 00000000 + // field XXXXXXXX YYYYYYXX RRRRYYYY ZZZRRRRR --ZZZZZZ HHHHHHHH BBBBBBBB ----BBBB + // bit no. 76543210 54321098 32109876 21087654 --876543 76543210 76543210 ----BA98 + // + // X = X-axis: b0111101001 = 489 + // Y = Y-axis: b1000001100 = 524 + // R = Rz-axis: b011111110 = 254 + // Z = Z-axis: b111111111 = 511 + // H = hat: b00000000 = 0 (centered) + // B = buttons b000000000000 = 0 (no buttons pressed) + // - = padding + + uint8_t report[] = {0xe9, 0x31, 0xe8, 0xef, 0x3f, 0x00, 0x00, 0x00}; + tusb_hid_simple_joysick_process_report(simple_joystick, report, sizeof(report)); + TEST_ASSERT_EQUAL(true, simple_joystick->has_values); + TEST_ASSERT_EQUAL(489, simple_joystick->values.x1); + TEST_ASSERT_EQUAL(524, simple_joystick->values.y1); + TEST_ASSERT_EQUAL(511, simple_joystick->values.x2); + TEST_ASSERT_EQUAL(254, simple_joystick->values.y2); + TEST_ASSERT_EQUAL(0, simple_joystick->values.hat); + TEST_ASSERT_EQUAL(0, simple_joystick->values.buttons); + + TEST_ASSERT_EQUAL(8, simple_joystick->report_length); +} + +void test_get_simple_joysticks(void) { + static tusb_hid_simple_joysick_t* hid_simple_joysicks[4]; + uint8_t jcount; + jcount = tuh_hid_get_simple_joysticks(hid_simple_joysicks, 4); + TEST_ASSERT_EQUAL(0, jcount); + tuh_hid_joystick_parse_report_descriptor(tb_speedlink, sizeof(tb_speedlink), 5, 9); + jcount = tuh_hid_get_simple_joysticks(hid_simple_joysicks, 4); + TEST_ASSERT_EQUAL(1, jcount); + TEST_ASSERT_EQUAL(tuh_hid_get_simple_joystick(5, 9, 0), hid_simple_joysicks[0]); + tuh_hid_joystick_parse_report_descriptor(tb_apple, sizeof(tb_apple), 1, 3); + jcount = tuh_hid_get_simple_joysticks(hid_simple_joysicks, 1); + TEST_ASSERT_EQUAL(1, jcount); + jcount = tuh_hid_get_simple_joysticks(hid_simple_joysicks, 4); + TEST_ASSERT_EQUAL(2, jcount); + TEST_ASSERT_EQUAL(tuh_hid_get_simple_joystick(5, 9, 0), hid_simple_joysicks[0]); + TEST_ASSERT_EQUAL(tuh_hid_get_simple_joystick(1, 3, 0), hid_simple_joysicks[1]); + tuh_hid_free_simple_joysticks_for_instance(5, 9); + jcount = tuh_hid_get_simple_joysticks(hid_simple_joysicks, 4); + TEST_ASSERT_EQUAL(1, jcount); + TEST_ASSERT_EQUAL(tuh_hid_get_simple_joystick(1, 3, 0), hid_simple_joysicks[0]); +} + +// TODO Test with report ID +// anyone got a joystick that reports with an ID? + + + diff --git a/test/test/examples/host/cdc_msc_hid/test_hid_host_utils.c b/test/test/examples/host/cdc_msc_hid/test_hid_host_utils.c new file mode 100644 index 0000000000..5805d33e59 --- /dev/null +++ b/test/test/examples/host/cdc_msc_hid/test_hid_host_utils.c @@ -0,0 +1,97 @@ +/* + * The MIT License (MIT) + * + * 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. + */ + + +#include "unity.h" + +// Files to test +#include "hid_host_utils.h" +TEST_FILE("hid_host_utils.c") + +void setUp(void) +{ +} + +void tearDown(void) +{ +} + +//--------------------------------------------------------------------+ +// Tests +//--------------------------------------------------------------------+ + +void test_tuh_hid_report_bits_u32(void) { + const uint8_t const tb[] = { + 0x50, 0x05, 0x8f, 0xff + }; + TEST_ASSERT_EQUAL(0x55, tuh_hid_report_bits_u32(tb, 4, 8)); + TEST_ASSERT_EQUAL(0x8f, tuh_hid_report_bits_u32(tb, 16, 8)); + TEST_ASSERT_EQUAL((uint32_t)0xff8f0550UL, tuh_hid_report_bits_u32(tb, 0, 32)); +} + +void test_tuh_hid_report_bits_i32(void) { + const uint8_t const tb[] = { + 0x50, 0x05, 0x8f, 0xff + }; + TEST_ASSERT_EQUAL(0x55, tuh_hid_report_bits_i32(tb, 4, 8)); + TEST_ASSERT_EQUAL(-113, tuh_hid_report_bits_i32(tb, 16, 8)); // int32_t 0xffffff8f == -113 + TEST_ASSERT_EQUAL(-7404208, tuh_hid_report_bits_i32(tb, 0, 32)); // int32_t 0xff8f0550 == -7404208 +} + +void test_tuh_hid_report_bytes_u32(void) { + const uint8_t const tb[] = { + 0x50, 0x05, 0x8f, 0xff + }; + TEST_ASSERT_EQUAL(0x0550, tuh_hid_report_bytes_u32(tb, 0, 2)); + TEST_ASSERT_EQUAL(0x8f05, tuh_hid_report_bytes_u32(tb, 1, 2)); + TEST_ASSERT_EQUAL(0xff8f05, tuh_hid_report_bytes_u32(tb, 1, 3)); + TEST_ASSERT_EQUAL(0xff8f0550, tuh_hid_report_bytes_u32(tb, 0, 4)); + TEST_ASSERT_EQUAL(0x8f, tuh_hid_report_bytes_u32(tb, 2, 1)); +} + +void test_tuh_hid_report_bytes_i32(void) { + const uint8_t const tb[] = { + 0x50, 0x05, 0x8f, 0xff + }; + TEST_ASSERT_EQUAL(0x0550, tuh_hid_report_bytes_i32(tb, 0, 2)); + TEST_ASSERT_EQUAL(-28923, tuh_hid_report_bytes_i32(tb, 1, 2)); // int32_t 0xffff8f05 == -28923 + TEST_ASSERT_EQUAL(-28923, tuh_hid_report_bytes_i32(tb, 1, 3)); // int32_t 0xffff8f05 == -28923 + TEST_ASSERT_EQUAL(-7404208, tuh_hid_report_bytes_i32(tb, 0, 4)); // int32_t 0xff8f0550 == -7404208 + TEST_ASSERT_EQUAL(-113, tuh_hid_report_bytes_i32(tb, 2, 1)); // int32_t 0xffffff8f == -113 +} + +void test_tuh_hid_report_i32(void) { + const uint8_t const tb[] = { + 0x50, 0x05, 0x8f, 0xff + }; + TEST_ASSERT_EQUAL(0x55, tuh_hid_report_i32(tb, 4, 8, false)); + TEST_ASSERT_EQUAL(0x8f, tuh_hid_report_i32(tb, 16, 8, false)); + TEST_ASSERT_EQUAL(-7404208, tuh_hid_report_i32(tb, 0, 32, false)); // int32_t 0xff8f0550 == -7404208 + + TEST_ASSERT_EQUAL(0x55, tuh_hid_report_i32(tb, 4, 8, true)); + TEST_ASSERT_EQUAL(-113, tuh_hid_report_i32(tb, 16, 8, true)); // int32_t 0xffffff8f == -113 + TEST_ASSERT_EQUAL(-7404208, tuh_hid_report_i32(tb, 0, 32, true)); // int32_t 0xff8f0550 == -7404208 +} + + diff --git a/tools/iar_template.ipcf b/tools/iar_template.ipcf index ba54fe0578..fe836cc701 100644 --- a/tools/iar_template.ipcf +++ b/tools/iar_template.ipcf @@ -31,6 +31,8 @@ $TUSB_DIR$/src/class/hid/hid_device.c + $TUSB_DIR$/src/class/hid/hid_ri.c + $TUSB_DIR$/src/class/hid/hid_rip.c $TUSB_DIR$/src/class/hid/hid_host.c