From 04b622a2758b5804003135199ecae206990ce6c5 Mon Sep 17 00:00:00 2001 From: Jacob Alexander Date: Tue, 21 Jan 2020 11:13:55 -0800 Subject: [PATCH 01/11] Usage Page and Usage on Linux with hidraw Adapted from ApeironTuska's PR to PastaJ36/hidapi (https://github.com/PastaJ36/hidapi/pull/1) Which was adapted from djpnewton's PR to signal11/hidapi (https://github.com/signal11/hidapi/pull/6) Also addresses some of the issues mentioned in (https://github.com/signal11/hidapi/pull/6) * hid_open_path and hid_enumerate both need to retrieve the usage page as the user may call hid_open_path directly without using hid_enumerate * Added get_hid_item_size() for hid parsing, used in both uses_numbered_reports() and get_hid_usage() NOTE: This commit does not handle composite HID descriptors I am interested in adding support for composite descriptors though I still need to find a device with a composite descriptor to test it correctly - hidtest-hidraw test - Device Found type: 308f 0015 path: /dev/hidraw10 serial_number: 53373100323943353230353139363032 - sam4s2 Manufacturer: Kiibohd Product: Keyboard - None PartialMap USBxUART Release: 4ac Interface: 0 Usage (page): 0x6 (0x1) Device Found type: 308f 0015 path: /dev/hidraw11 serial_number: 53373100323943353230353139363032 - sam4s2 Manufacturer: Kiibohd Product: Keyboard - None PartialMap USBxUART Release: 4ac Interface: 1 Usage (page): 0x6 (0x1) Device Found type: 308f 0015 path: /dev/hidraw12 serial_number: 53373100323943353230353139363032 - sam4s2 Manufacturer: Kiibohd Product: Keyboard - None PartialMap USBxUART Release: 4ac Interface: 2 Usage (page): 0x1 (0xc) Device Found type: 308f 0015 path: /dev/hidraw13 serial_number: 53373100323943353230353139363032 - sam4s2 Manufacturer: Kiibohd Product: Keyboard - None PartialMap USBxUART Release: 4ac Interface: 3 Usage (page): 0x2 (0x1) Device Found type: 308f 0015 path: /dev/hidraw14 serial_number: 53373100323943353230353139363032 - sam4s2 Manufacturer: Kiibohd Product: Keyboard - None PartialMap USBxUART Release: 4ac Interface: 4 Usage (page): 0x1100 (0xff1c) --- hidapi/hidapi.h | 6 +- linux/hid.c | 239 +++++++++++++++++++++++++++++++++++++----------- 2 files changed, 191 insertions(+), 54 deletions(-) diff --git a/hidapi/hidapi.h b/hidapi/hidapi.h index c8f7bc50a..83b669f08 100644 --- a/hidapi/hidapi.h +++ b/hidapi/hidapi.h @@ -96,10 +96,10 @@ extern "C" { /** Product string */ wchar_t *product_string; /** Usage Page for this Device/Interface - (Windows/Mac only). */ + (Windows/Mac/hidraw only) */ unsigned short usage_page; /** Usage for this Device/Interface - (Windows/Mac only).*/ + (Windows/Mac/hidraw only) */ unsigned short usage; /** The USB interface which this logical device represents. @@ -124,7 +124,7 @@ extern "C" { needed. This function should be called at the beginning of execution however, if there is a chance of HIDAPI handles being opened by different threads simultaneously. - + @ingroup API @returns diff --git a/linux/hid.c b/linux/hid.c index 90f688b42..4ec5fabc8 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -123,6 +123,19 @@ static void register_global_error(const char *msg) last_global_error_str = utf8_to_wchar_t(msg); } +/* See register_global_error, but you can pass a format string into this function. */ +static void register_global_error_format(const char *format, ...) +{ + va_list args; + va_start(args, format); + + char msg[100]; + vsnprintf(msg, sizeof(msg), format, args); + + va_end(args); + + register_global_error(msg); +} /* Set the last error for a device to be reported by hid_error(device). * The given error message will be copied (and decoded according to the @@ -158,11 +171,67 @@ static wchar_t *copy_udev_string(struct udev_device *dev, const char *udev_name) return utf8_to_wchar_t(udev_device_get_sysattr_value(dev, udev_name)); } +/* + * Gets the size of the HID item at the given position + * Returns 1 if successful, 0 if an invalid key + * Sets data_len and key_size when successful + */ +static int get_hid_item_size(__u8 *report_descriptor, unsigned int pos, __u32 size, int *data_len, int *key_size) +{ + int key = report_descriptor[pos]; + int size_code; + + /* + * This is a Long Item. The next byte contains the + * length of the data section (value) for this key. + * See the HID specification, version 1.11, section + * 6.2.2.3, titled "Long Items." + */ + if ((key & 0xf0) == 0xf0) { + if (pos + 1 < size) + { + *data_len = report_descriptor[pos + 1]; + *key_size = 3; + return 1; + } + *data_len = 0; /* malformed report */ + *key_size = 0; + } + + /* + * This is a Short Item. The bottom two bits of the + * key contain the size code for the data section + * (value) for this key. Refer to the HID + * specification, version 1.11, section 6.2.2.2, + * titled "Short Items." + */ + size_code = key & 0x3; + switch (size_code) { + case 0: + case 1: + case 2: + *data_len = size_code; + *key_size = 1; + return 1; + case 3: + *data_len = 4; + *key_size = 1; + return 1; + default: + /* Can't ever happen since size_code is & 0x3 */ + *data_len = 0; + *key_size = 0; + break; + }; + + /* malformed report */ + return 0; +} + /* uses_numbered_reports() returns 1 if report_descriptor describes a device which contains numbered reports. */ static int uses_numbered_reports(__u8 *report_descriptor, __u32 size) { unsigned int i = 0; - int size_code; int data_len, key_size; while (i < size) { @@ -175,42 +244,9 @@ static int uses_numbered_reports(__u8 *report_descriptor, __u32 size) { return 1; } - //printf("key: %02hhx\n", key); - - if ((key & 0xf0) == 0xf0) { - /* This is a Long Item. The next byte contains the - length of the data section (value) for this key. - See the HID specification, version 1.11, section - 6.2.2.3, titled "Long Items." */ - if (i+1 < size) - data_len = report_descriptor[i+1]; - else - data_len = 0; /* malformed report */ - key_size = 3; - } - else { - /* This is a Short Item. The bottom two bits of the - key contain the size code for the data section - (value) for this key. Refer to the HID - specification, version 1.11, section 6.2.2.2, - titled "Short Items." */ - size_code = key & 0x3; - switch (size_code) { - case 0: - case 1: - case 2: - data_len = size_code; - break; - case 3: - data_len = 4; - break; - default: - /* Can't ever happen since size_code is & 0x3 */ - data_len = 0; - break; - }; - key_size = 1; - } + /* Determine data_len and key_size */ + if (!get_hid_item_size(report_descriptor, i, size, &data_len, &key_size)) + return 0; /* malformed report */ /* Skip over this key and it's associated data */ i += data_len + key_size; @@ -220,6 +256,99 @@ static int uses_numbered_reports(__u8 *report_descriptor, __u32 size) { return 0; } +/* + * Get bytes from a HID Report Descriptor. + * Only call with a num_bytes of 0, 1, 2, or 4. + */ +static __u32 get_hid_report_bytes(__u8 *rpt, size_t len, size_t num_bytes, size_t cur) +{ + /* Return if there aren't enough bytes. */ + if (cur + num_bytes >= len) + return 0; + + if (num_bytes == 0) + return 0; + else if (num_bytes == 1) + return rpt[cur + 1]; + else if (num_bytes == 2) + return (rpt[cur + 2] * 256 + rpt[cur + 1]); + else if (num_bytes == 4) + return ( + rpt[cur + 4] * 0x01000000 + + rpt[cur + 3] * 0x00010000 + + rpt[cur + 2] * 0x00000100 + + rpt[cur + 1] * 0x00000001 + ); + else + return 0; +} + +/* + * Retrieves the device's Usage Page and Usage from the report + * descriptor. The algorithm is simple, as it just returns the first + * Usage and Usage Page that it finds in the descriptor. + * The return value is 0 on success and -1 on failure. + */ +static int get_hid_usage(__u8 *report_descriptor, __u32 size, unsigned short *usage_page, unsigned short *usage) +{ + unsigned int i = 0; + int data_len, key_size; + int usage_found = 0, usage_page_found = 0; + + while (i < size) { + int key = report_descriptor[i]; + int key_cmd = key & 0xfc; + + /* Determine data_len and key_size */ + if (!get_hid_item_size(report_descriptor, i, size, &data_len, &key_size)) + return -1; /* malformed report */ + + if (key_cmd == 0x4) { + *usage_page = get_hid_report_bytes(report_descriptor, size, data_len, i); + usage_page_found = 1; + } + if (key_cmd == 0x8) { + *usage = get_hid_report_bytes(report_descriptor, size, data_len, i); + usage_found = 1; + } + + if (usage_page_found && usage_found) + return 0; /* success */ + + /* Skip over this key and it's associated data */ + i += data_len + key_size; + } + + return -1; /* failure */ +} + +/* + * Retrieves the hidraw report descriptor + */ +static int get_hid_report_descriptor(int device_handle, struct hidraw_report_descriptor *rpt_desc) +{ + int res, desc_size = 0; + + memset(rpt_desc, 0x0, sizeof(*rpt_desc)); + + /* Get Report Descriptor Size */ + res = ioctl(device_handle, HIDIOCGRDESCSIZE, &desc_size); + if (res < 0) { + register_global_error_format("ioctl (GRDESCSIZE): %s", strerror(errno)); + return res; + } + + /* Get Report Descriptor */ + rpt_desc->size = desc_size; + res = ioctl(device_handle, HIDIOCGRDESC, rpt_desc); + if (res < 0) { + register_global_error_format("ioctl (GRDESC): %s", strerror(errno)); + return res; + } + + return res; +} + /* * The caller is responsible for free()ing the (newly-allocated) character * strings pointed to by serial_number_utf8 and product_name_utf8 after use. @@ -554,6 +683,25 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, cur_dev->manufacturer_string = copy_udev_string(usb_dev, device_string_names[DEVICE_STRING_MANUFACTURER]); cur_dev->product_string = copy_udev_string(usb_dev, device_string_names[DEVICE_STRING_PRODUCT]); + /* Usage Page and Usage */ + int res; + struct hidraw_report_descriptor rpt_desc; + int device_handle = open(dev_path, O_RDWR); + if (device_handle > 0) { + res = get_hid_report_descriptor(device_handle, &rpt_desc); + if (res >= 0) { + unsigned short page = 0, usage = 0; + /* + * Parse the usage and usage page + * out of the report descriptor. + */ + get_hid_usage(rpt_desc.value, rpt_desc.size, &page, &usage); + cur_dev->usage_page = page; + cur_dev->usage = usage; + } + close(device_handle); + } + /* Release Number */ str = udev_device_get_sysattr_value(usb_dev, "bcdDevice"); cur_dev->release_number = (str)? strtol(str, NULL, 16): 0x0; @@ -673,22 +821,11 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) register_device_error(dev, NULL); /* Get the report descriptor */ - int res, desc_size = 0; + int res; struct hidraw_report_descriptor rpt_desc; - memset(&rpt_desc, 0x0, sizeof(rpt_desc)); - - /* Get Report Descriptor Size */ - res = ioctl(dev->device_handle, HIDIOCGRDESCSIZE, &desc_size); - if (res < 0) - register_device_error_format(dev, "ioctl (GRDESCSIZE): %s", strerror(errno)); - - /* Get Report Descriptor */ - rpt_desc.size = desc_size; - res = ioctl(dev->device_handle, HIDIOCGRDESC, &rpt_desc); - if (res < 0) { - register_device_error_format(dev, "ioctl (GRDESC): %s", strerror(errno)); - } else { + res = get_hid_report_descriptor(dev->device_handle, &rpt_desc); + if (res >= 0) { /* Determine if this device uses numbered reports. */ dev->uses_numbered_reports = uses_numbered_reports(rpt_desc.value, From b0d71f1b4f3907e0b7982f775eed17b418793a5c Mon Sep 17 00:00:00 2001 From: Jacob Alexander Date: Thu, 23 Jan 2020 01:33:50 -0800 Subject: [PATCH 02/11] Adding Usage Page and Usage support for Bluetooth with hidraw - All hidraw devices have access to the get descriptor ioctl so this should work in general --- linux/hid.c | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/linux/hid.c b/linux/hid.c index 4ec5fabc8..ea5e5ad4a 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -648,6 +648,25 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, /* Interface Number */ cur_dev->interface_number = -1; + /* Usage Page and Usage */ + int res; + struct hidraw_report_descriptor rpt_desc; + int device_handle = open(dev_path, O_RDWR); + if (device_handle > 0) { + res = get_hid_report_descriptor(device_handle, &rpt_desc); + if (res >= 0) { + unsigned short page = 0, usage = 0; + /* + * Parse the usage and usage page + * out of the report descriptor. + */ + get_hid_usage(rpt_desc.value, rpt_desc.size, &page, &usage); + cur_dev->usage_page = page; + cur_dev->usage = usage; + } + close(device_handle); + } + switch (bus_type) { case BUS_USB: /* The device pointed to by raw_dev contains information about @@ -683,25 +702,6 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, cur_dev->manufacturer_string = copy_udev_string(usb_dev, device_string_names[DEVICE_STRING_MANUFACTURER]); cur_dev->product_string = copy_udev_string(usb_dev, device_string_names[DEVICE_STRING_PRODUCT]); - /* Usage Page and Usage */ - int res; - struct hidraw_report_descriptor rpt_desc; - int device_handle = open(dev_path, O_RDWR); - if (device_handle > 0) { - res = get_hid_report_descriptor(device_handle, &rpt_desc); - if (res >= 0) { - unsigned short page = 0, usage = 0; - /* - * Parse the usage and usage page - * out of the report descriptor. - */ - get_hid_usage(rpt_desc.value, rpt_desc.size, &page, &usage); - cur_dev->usage_page = page; - cur_dev->usage = usage; - } - close(device_handle); - } - /* Release Number */ str = udev_device_get_sysattr_value(usb_dev, "bcdDevice"); cur_dev->release_number = (str)? strtol(str, NULL, 16): 0x0; From 015296e30cf6693e79b6c231c304480bc23b1ba5 Mon Sep 17 00:00:00 2001 From: Jacob Alexander Date: Fri, 24 Jan 2020 23:23:24 -0800 Subject: [PATCH 03/11] hidraw support for multiple usage/usage-pages - Each usage/usage-pair is returned if the following conditions are met 1. Usage (Local Item) has not been consumed by a Main Item and has been set 2. A collection is started (A collection without a dedicated Usage is ignored, follows macOS behaviour) - Tested on USB and Bluetooth devices ** USB (Keyboard with Mouse Descriptor) ** Device Found type: 308f 0013 path: /dev/hidraw0 serial_number: 5337310036384B323430313035353031 - sam4s8 Manufacturer: Input Club Product: Keyboard - Kira PixelMap USB Release: 48b Interface: 0 Usage (page): 0x6 (0x1) Device Found type: 308f 0013 path: /dev/hidraw1 serial_number: 5337310036384B323430313035353031 - sam4s8 Manufacturer: Input Club Product: Keyboard - Kira PixelMap USB Release: 48b Interface: 1 Usage (page): 0x6 (0x1) Device Found type: 308f 0013 path: /dev/hidraw2 serial_number: 5337310036384B323430313035353031 - sam4s8 Manufacturer: Input Club Product: Keyboard - Kira PixelMap USB Release: 48b Interface: 2 Usage (page): 0x1 (0xc) Device Found type: 308f 0013 path: /dev/hidraw4 serial_number: 5337310036384B323430313035353031 - sam4s8 Manufacturer: Input Club Product: Keyboard - Kira PixelMap USB Release: 48b Interface: 5 Usage (page): 0x2 (0x1) Device Found type: 308f 0013 path: /dev/hidraw4 serial_number: 5337310036384B323430313035353031 - sam4s8 Manufacturer: Input Club Product: Keyboard - Kira PixelMap USB Release: 48b Interface: 5 Usage (page): 0x1 (0x1) ** Bluetooth (Keyboard with Composite Descriptor) ** Device Found type: 093a 2801 path: /dev/hidraw12 serial_number: DA:33:7F:D0:18:40 Manufacturer: Product: Hexgears-BK2 Release: 0 Interface: -1 Usage (page): 0x6 (0x1) Device Found type: 093a 2801 path: /dev/hidraw12 serial_number: DA:33:7F:D0:18:40 Manufacturer: Product: Hexgears-BK2 Release: 0 Interface: -1 Usage (page): 0x2 (0x1) Device Found type: 093a 2801 path: /dev/hidraw12 serial_number: DA:33:7F:D0:18:40 Manufacturer: Product: Hexgears-BK2 Release: 0 Interface: -1 Usage (page): 0x1 (0x1) Device Found type: 093a 2801 path: /dev/hidraw12 serial_number: DA:33:7F:D0:18:40 Manufacturer: Product: Hexgears-BK2 Release: 0 Interface: -1 Usage (page): 0x1 (0xc) Device Found type: 093a 2801 path: /dev/hidraw12 serial_number: DA:33:7F:D0:18:40 Manufacturer: Product: Hexgears-BK2 Release: 0 Interface: -1 Usage (page): 0x80 (0x1) Device Found type: 093a 2801 path: /dev/hidraw12 serial_number: DA:33:7F:D0:18:40 Manufacturer: Product: Hexgears-BK2 Release: 0 Interface: -1 Usage (page): 0x1 (0xff00) --- linux/hid.c | 146 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 106 insertions(+), 40 deletions(-) diff --git a/linux/hid.c b/linux/hid.c index ea5e5ad4a..ac5670770 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -284,42 +284,81 @@ static __u32 get_hid_report_bytes(__u8 *rpt, size_t len, size_t num_bytes, size_ } /* - * Retrieves the device's Usage Page and Usage from the report - * descriptor. The algorithm is simple, as it just returns the first - * Usage and Usage Page that it finds in the descriptor. - * The return value is 0 on success and -1 on failure. + * Retrieves the device's Usage Page and Usage from the report descriptor. + * The algorithm returns the current Usage Page/Usage pair whenever a new + * Collection is found and a Usage Local Item is currently in scope. + * Usage Local Items are consumed by each Main Item (See. 6.2.2.8). + * The algorithm should give similar results as Apple's: + * https://developer.apple.com/documentation/iokit/kiohiddeviceusagepairskey?language=objc + * Physical Collections are also matched (macOS does the same). + * + * This function can be called repeatedly until it returns non-0 + * Usage is found. pos is the starting point (initially 0) and will be updated + * to the next search position. + * + * The return value is 0 when a pair is found. + * 1 when finished processing descriptor. + * -1 on a malformed report. */ -static int get_hid_usage(__u8 *report_descriptor, __u32 size, unsigned short *usage_page, unsigned short *usage) +static int get_next_hid_usage(__u8 *report_descriptor, __u32 size, unsigned int *pos, unsigned short *usage_page, unsigned short *usage) { - unsigned int i = 0; int data_len, key_size; - int usage_found = 0, usage_page_found = 0; + int initial = *pos == 0; /* Used to handle case where no top-level application collection is defined */ + int usage_pair_ready = 0; - while (i < size) { - int key = report_descriptor[i]; + /* Usage is a Local Item, it must be set before each Main Item (Collection) before a pair is returned */ + int usage_found = 0; + + while (*pos < size) { + int key = report_descriptor[*pos]; int key_cmd = key & 0xfc; /* Determine data_len and key_size */ - if (!get_hid_item_size(report_descriptor, i, size, &data_len, &key_size)) + if (!get_hid_item_size(report_descriptor, *pos, size, &data_len, &key_size)) return -1; /* malformed report */ - if (key_cmd == 0x4) { - *usage_page = get_hid_report_bytes(report_descriptor, size, data_len, i); - usage_page_found = 1; - } - if (key_cmd == 0x8) { - *usage = get_hid_report_bytes(report_descriptor, size, data_len, i); + switch (key_cmd) { + case 0x4: /* Usage Page 6.2.2.7 (Global) */ + *usage_page = get_hid_report_bytes(report_descriptor, size, data_len, *pos); + break; + + case 0x8: /* Usage 6.2.2.8 (Local) */ + *usage = get_hid_report_bytes(report_descriptor, size, data_len, *pos); usage_found = 1; + break; + + case 0xa0: /* Collection 6.2.2.4 (Main) */ + /* A Usage Item (Local) must be found for the pair to be valid */ + if (usage_found) + usage_pair_ready = 1; + + /* Usage is a Local Item, unset it */ + usage_found = 0; + break; + + case 0x80: /* Input 6.2.2.4 (Main) */ + case 0x90: /* Output 6.2.2.4 (Main) */ + case 0xb0: /* Feature 6.2.2.4 (Main) */ + case 0xc0: /* End Collection 6.2.2.4 (Main) */ + /* Usage is a Local Item, unset it */ + usage_found = 0; + break; } - if (usage_page_found && usage_found) - return 0; /* success */ - /* Skip over this key and it's associated data */ - i += data_len + key_size; + *pos += data_len + key_size; + + /* Return usage pair */ + if (usage_pair_ready) + return 0; } - return -1; /* failure */ + /* If no top-level application collection is found and usage page/usage pair is found, pair is valid + https://docs.microsoft.com/en-us/windows-hardware/drivers/hid/top-level-collections */ + if (initial && usage_found) + return 0; /* success */ + + return 1; /* finished processing */ } /* @@ -648,25 +687,6 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, /* Interface Number */ cur_dev->interface_number = -1; - /* Usage Page and Usage */ - int res; - struct hidraw_report_descriptor rpt_desc; - int device_handle = open(dev_path, O_RDWR); - if (device_handle > 0) { - res = get_hid_report_descriptor(device_handle, &rpt_desc); - if (res >= 0) { - unsigned short page = 0, usage = 0; - /* - * Parse the usage and usage page - * out of the report descriptor. - */ - get_hid_usage(rpt_desc.value, rpt_desc.size, &page, &usage); - cur_dev->usage_page = page; - cur_dev->usage = usage; - } - close(device_handle); - } - switch (bus_type) { case BUS_USB: /* The device pointed to by raw_dev contains information about @@ -730,6 +750,52 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, * check for USB and Bluetooth devices above */ break; } + + /* Usage Page and Usage */ + int res; + struct hidraw_report_descriptor rpt_desc; + int device_handle = open(dev_path, O_RDWR); + if (device_handle > 0) { + res = get_hid_report_descriptor(device_handle, &rpt_desc); + if (res >= 0) { + unsigned short page = 0, usage = 0; + unsigned int pos = 0, usage_count = 0; + /* + * Parse the usage and usage page + * out of the report descriptor. + */ + while (!get_next_hid_usage(rpt_desc.value, rpt_desc.size, &pos, &page, &usage)) { + usage_count++; + + /* First usage */ + if (usage_count == 1) + { + cur_dev->usage_page = page; + cur_dev->usage = usage; + continue; + } + + /* Create new record for additional usage pairs */ + tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); + cur_dev->next = tmp; + prev_dev = cur_dev; + cur_dev = tmp; + + /* Update fields */ + cur_dev->path = strdup(dev_path); + cur_dev->vendor_id = dev_vid; + cur_dev->product_id = dev_pid; + cur_dev->serial_number = utf8_to_wchar_t(serial_number_utf8); + cur_dev->release_number = prev_dev->release_number; + cur_dev->interface_number = prev_dev->interface_number; + cur_dev->manufacturer_string = prev_dev->manufacturer_string? wcsdup(prev_dev->manufacturer_string): NULL; + cur_dev->product_string = prev_dev->product_string? wcsdup(prev_dev->product_string): NULL; + cur_dev->usage_page = page; + cur_dev->usage = usage; + } + } + close(device_handle); + } } next: From ab2516fcbae280c42911f6704537ff2fe4afd409 Mon Sep 17 00:00:00 2001 From: Jacob Alexander Date: Wed, 18 Nov 2020 11:57:43 -0800 Subject: [PATCH 04/11] Small fixes for hidraw Usage Page and Usage - O_RDONLY is sufficient to retrieve usage and usage page - File descriptor fix per @Youw's suggestion --- linux/hid.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/linux/hid.c b/linux/hid.c index ac5670770..70c0d314c 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -754,8 +754,8 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, /* Usage Page and Usage */ int res; struct hidraw_report_descriptor rpt_desc; - int device_handle = open(dev_path, O_RDWR); - if (device_handle > 0) { + int device_handle = open(dev_path, O_RDONLY); + if (device_handle >= 0) { res = get_hid_report_descriptor(device_handle, &rpt_desc); if (res >= 0) { unsigned short page = 0, usage = 0; From 2bbc30dffc4311ab5225efc8113f934509fe3f0c Mon Sep 17 00:00:00 2001 From: Jacob Alexander Date: Wed, 18 Nov 2020 17:25:07 -0800 Subject: [PATCH 05/11] Rewriting get_hid_report_descriptor to use sysfs instead of ioctls - Elevated permissions are no longer required to query usage and usage pages - Some additional work is required when opening a /dev/hidraw device in order to locate the sysfs path * Needed to determine if we're using numbered reports or not --- linux/hid.c | 124 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 71 insertions(+), 53 deletions(-) diff --git a/linux/hid.c b/linux/hid.c index 70c0d314c..570a2ff94 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -364,27 +365,30 @@ static int get_next_hid_usage(__u8 *report_descriptor, __u32 size, unsigned int /* * Retrieves the hidraw report descriptor */ -static int get_hid_report_descriptor(int device_handle, struct hidraw_report_descriptor *rpt_desc) +static int get_hid_report_descriptor(char *rpt_path, struct hidraw_report_descriptor *rpt_desc) { - int res, desc_size = 0; + int res, rpt_handle; - memset(rpt_desc, 0x0, sizeof(*rpt_desc)); - - /* Get Report Descriptor Size */ - res = ioctl(device_handle, HIDIOCGRDESCSIZE, &desc_size); - if (res < 0) { - register_global_error_format("ioctl (GRDESCSIZE): %s", strerror(errno)); - return res; + rpt_handle = open(rpt_path, O_RDONLY); + if (rpt_handle == -1) { + register_global_error(strerror(errno)); + return -1; } - /* Get Report Descriptor */ - rpt_desc->size = desc_size; - res = ioctl(device_handle, HIDIOCGRDESC, rpt_desc); - if (res < 0) { - register_global_error_format("ioctl (GRDESC): %s", strerror(errno)); - return res; + /* + * Read in the Report Descriptor + * The sysfs file has a maximum size of 4096 (which is the same as HID_MAX_DESCRIPTOR_SIZE) so we should always + * be ok when reading the descriptor. + * In practice if the HID descriptor is any larger I suspect many other things will break. + */ + memset(rpt_desc, 0x0, sizeof(*rpt_desc)); + res = read(rpt_handle, rpt_desc->value, HID_MAX_DESCRIPTOR_SIZE); + if (res == -1) { + register_global_error(strerror(errno)); } + rpt_desc->size = res; + close(rpt_handle); return res; } @@ -751,51 +755,52 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, break; } + /* Construct /device/report_descriptor */ + char *rpt_path = (char *)calloc(1, strlen(sysfs_path) + 25); + sprintf(rpt_path, "%s/device/report_descriptor", sysfs_path); + /* Usage Page and Usage */ int res; struct hidraw_report_descriptor rpt_desc; - int device_handle = open(dev_path, O_RDONLY); - if (device_handle >= 0) { - res = get_hid_report_descriptor(device_handle, &rpt_desc); - if (res >= 0) { - unsigned short page = 0, usage = 0; - unsigned int pos = 0, usage_count = 0; - /* - * Parse the usage and usage page - * out of the report descriptor. - */ - while (!get_next_hid_usage(rpt_desc.value, rpt_desc.size, &pos, &page, &usage)) { - usage_count++; - - /* First usage */ - if (usage_count == 1) - { - cur_dev->usage_page = page; - cur_dev->usage = usage; - continue; - } - - /* Create new record for additional usage pairs */ - tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); - cur_dev->next = tmp; - prev_dev = cur_dev; - cur_dev = tmp; - - /* Update fields */ - cur_dev->path = strdup(dev_path); - cur_dev->vendor_id = dev_vid; - cur_dev->product_id = dev_pid; - cur_dev->serial_number = utf8_to_wchar_t(serial_number_utf8); - cur_dev->release_number = prev_dev->release_number; - cur_dev->interface_number = prev_dev->interface_number; - cur_dev->manufacturer_string = prev_dev->manufacturer_string? wcsdup(prev_dev->manufacturer_string): NULL; - cur_dev->product_string = prev_dev->product_string? wcsdup(prev_dev->product_string): NULL; + res = get_hid_report_descriptor(rpt_path, &rpt_desc); + if (res >= 0) { + unsigned short page = 0, usage = 0; + unsigned int pos = 0, usage_count = 0; + /* + * Parse the usage and usage page + * out of the report descriptor. + */ + while (!get_next_hid_usage(rpt_desc.value, rpt_desc.size, &pos, &page, &usage)) { + usage_count++; + + /* First usage */ + if (usage_count == 1) + { cur_dev->usage_page = page; cur_dev->usage = usage; + continue; } + + /* Create new record for additional usage pairs */ + tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); + cur_dev->next = tmp; + prev_dev = cur_dev; + cur_dev = tmp; + + /* Update fields */ + cur_dev->path = strdup(dev_path); + cur_dev->vendor_id = dev_vid; + cur_dev->product_id = dev_pid; + cur_dev->serial_number = utf8_to_wchar_t(serial_number_utf8); + cur_dev->release_number = prev_dev->release_number; + cur_dev->interface_number = prev_dev->interface_number; + cur_dev->manufacturer_string = prev_dev->manufacturer_string? wcsdup(prev_dev->manufacturer_string): NULL; + cur_dev->product_string = prev_dev->product_string? wcsdup(prev_dev->product_string): NULL; + cur_dev->usage_page = page; + cur_dev->usage = usage; } - close(device_handle); } + free(rpt_path); } next: @@ -886,17 +891,30 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) /* Set device error to none */ register_device_error(dev, NULL); + /* Construct /device/report_descriptor using udev */ + char *sysname = basename((char*)path); + struct udev *udev; + struct udev_device *udev_dev; + udev = udev_new(); + udev_dev = udev_device_new_from_subsystem_sysname(udev, "hidraw", sysname); + const char *sysfs_path = udev_device_get_syspath(udev_dev); + char *rpt_path = (char *)calloc(1, strlen(sysfs_path) + 25); + sprintf(rpt_path, "%s/device/report_descriptor", sysfs_path); + udev_device_unref(udev_dev); + udev_unref(udev); + /* Get the report descriptor */ int res; struct hidraw_report_descriptor rpt_desc; - res = get_hid_report_descriptor(dev->device_handle, &rpt_desc); + res = get_hid_report_descriptor(rpt_path, &rpt_desc); if (res >= 0) { /* Determine if this device uses numbered reports. */ dev->uses_numbered_reports = uses_numbered_reports(rpt_desc.value, rpt_desc.size); } + free(rpt_path); return dev; } From e68a29c7afee337a50fd3e9d3348af431fdbaade Mon Sep 17 00:00:00 2001 From: Jacob Alexander Date: Wed, 18 Nov 2020 22:10:25 -0800 Subject: [PATCH 06/11] Resolving a possible symlink before using basename to find hidraw* - In most cases enumeration will be used to get the absolute path to /dev/hidraw* However it is possible to use a symlink to open the hidraw interface --- linux/hid.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/linux/hid.c b/linux/hid.c index 570a2ff94..14df5dd48 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -891,8 +891,15 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) /* Set device error to none */ register_device_error(dev, NULL); - /* Construct /device/report_descriptor using udev */ - char *sysname = basename((char*)path); + /* + * Construct /device/report_descriptor using udev + * First we need to make sure the incoming path is the file path (and not a symlink). + * From there we'll take the final /dev/hidraw* and take the basename to get hidraw*. + * With hidraw* we can use udev to locate the sysfs path to the hidraw device. + * And then finally locate the report_descriptor file. + */ + char *fullpath = realpath(path, NULL); + char *sysname = basename(fullpath); struct udev *udev; struct udev_device *udev_dev; udev = udev_new(); @@ -914,6 +921,7 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) uses_numbered_reports(rpt_desc.value, rpt_desc.size); } + free(fullpath); free(rpt_path); return dev; From e4a39dea0315dec36013778cd6d5ec22875d1f06 Mon Sep 17 00:00:00 2001 From: Jacob Alexander Date: Thu, 19 Nov 2020 10:14:14 -0800 Subject: [PATCH 07/11] Use ioctls to find number reports when opening hidraw device - Code review fixes - Simplify get_next_hid_usage loops --- linux/hid.c | 74 +++++++++++++++++++++++------------------------------ 1 file changed, 32 insertions(+), 42 deletions(-) diff --git a/linux/hid.c b/linux/hid.c index 14df5dd48..65e137614 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -25,7 +25,6 @@ #include #include #include -#include #include #include @@ -363,7 +362,8 @@ static int get_next_hid_usage(__u8 *report_descriptor, __u32 size, unsigned int } /* - * Retrieves the hidraw report descriptor + * Retrieves the hidraw report descriptor from a file. + * When using this form, /device/report_descriptor, elevated priviledges are not required. */ static int get_hid_report_descriptor(char *rpt_path, struct hidraw_report_descriptor *rpt_desc) { @@ -371,7 +371,7 @@ static int get_hid_report_descriptor(char *rpt_path, struct hidraw_report_descri rpt_handle = open(rpt_path, O_RDONLY); if (rpt_handle == -1) { - register_global_error(strerror(errno)); + register_global_error_format("open failed (%s): %s", rpt_path, strerror(errno)); return -1; } @@ -384,7 +384,7 @@ static int get_hid_report_descriptor(char *rpt_path, struct hidraw_report_descri memset(rpt_desc, 0x0, sizeof(*rpt_desc)); res = read(rpt_handle, rpt_desc->value, HID_MAX_DESCRIPTOR_SIZE); if (res == -1) { - register_global_error(strerror(errno)); + register_global_error_format("read failed (%s): %s", rpt_path, strerror(errno)); } rpt_desc->size = res; @@ -756,8 +756,9 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, } /* Construct /device/report_descriptor */ - char *rpt_path = (char *)calloc(1, strlen(sysfs_path) + 25); - sprintf(rpt_path, "%s/device/report_descriptor", sysfs_path); + int rpt_path_len = strlen(sysfs_path) + 25 + 1; + char *rpt_path = (char *)calloc(1, rpt_path_len); + snprintf(rpt_path, rpt_path_len, "%s/device/report_descriptor", sysfs_path); /* Usage Page and Usage */ int res; @@ -765,22 +766,21 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, res = get_hid_report_descriptor(rpt_path, &rpt_desc); if (res >= 0) { unsigned short page = 0, usage = 0; - unsigned int pos = 0, usage_count = 0; + unsigned int pos = 0; /* - * Parse the usage and usage page + * Parse the first usage and usage page * out of the report descriptor. */ while (!get_next_hid_usage(rpt_desc.value, rpt_desc.size, &pos, &page, &usage)) { - usage_count++; - - /* First usage */ - if (usage_count == 1) - { - cur_dev->usage_page = page; - cur_dev->usage = usage; - continue; - } + cur_dev->usage_page = page; + cur_dev->usage = usage; + } + /* + * Parse any additional usage and usage pages + * out of the report descriptor. + */ + while (!get_next_hid_usage(rpt_desc.value, rpt_desc.size, &pos, &page, &usage)) { /* Create new record for additional usage pairs */ tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); cur_dev->next = tmp; @@ -791,7 +791,7 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, cur_dev->path = strdup(dev_path); cur_dev->vendor_id = dev_vid; cur_dev->product_id = dev_pid; - cur_dev->serial_number = utf8_to_wchar_t(serial_number_utf8); + cur_dev->serial_number = wcsdup(prev_dev->serial_number); cur_dev->release_number = prev_dev->release_number; cur_dev->interface_number = prev_dev->interface_number; cur_dev->manufacturer_string = prev_dev->manufacturer_string? wcsdup(prev_dev->manufacturer_string): NULL; @@ -891,38 +891,28 @@ hid_device * HID_API_EXPORT hid_open_path(const char *path) /* Set device error to none */ register_device_error(dev, NULL); - /* - * Construct /device/report_descriptor using udev - * First we need to make sure the incoming path is the file path (and not a symlink). - * From there we'll take the final /dev/hidraw* and take the basename to get hidraw*. - * With hidraw* we can use udev to locate the sysfs path to the hidraw device. - * And then finally locate the report_descriptor file. - */ - char *fullpath = realpath(path, NULL); - char *sysname = basename(fullpath); - struct udev *udev; - struct udev_device *udev_dev; - udev = udev_new(); - udev_dev = udev_device_new_from_subsystem_sysname(udev, "hidraw", sysname); - const char *sysfs_path = udev_device_get_syspath(udev_dev); - char *rpt_path = (char *)calloc(1, strlen(sysfs_path) + 25); - sprintf(rpt_path, "%s/device/report_descriptor", sysfs_path); - udev_device_unref(udev_dev); - udev_unref(udev); - /* Get the report descriptor */ - int res; + int res, desc_size = 0; struct hidraw_report_descriptor rpt_desc; - res = get_hid_report_descriptor(rpt_path, &rpt_desc); - if (res >= 0) { + memset(&rpt_desc, 0x0, sizeof(rpt_desc)); + + /* Get Report Descriptor Size */ + res = ioctl(dev->device_handle, HIDIOCGRDESCSIZE, &desc_size); + if (res < 0) + register_device_error_format(dev, "ioctl (GRDESCSIZE): %s", strerror(errno)); + + /* Get Report Descriptor */ + rpt_desc.size = desc_size; + res = ioctl(dev->device_handle, HIDIOCGRDESC, &rpt_desc); + if (res < 0) { + register_device_error_format(dev, "ioctl (GRDESC): %s", strerror(errno)); + } else { /* Determine if this device uses numbered reports. */ dev->uses_numbered_reports = uses_numbered_reports(rpt_desc.value, rpt_desc.size); } - free(fullpath); - free(rpt_path); return dev; } From c9c96f852c52342bd78157459024c2e1cedf2556 Mon Sep 17 00:00:00 2001 From: Jacob Alexander Date: Thu, 19 Nov 2020 10:21:49 -0800 Subject: [PATCH 08/11] Fix --- linux/hid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linux/hid.c b/linux/hid.c index 65e137614..06f2e18af 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -771,7 +771,7 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, * Parse the first usage and usage page * out of the report descriptor. */ - while (!get_next_hid_usage(rpt_desc.value, rpt_desc.size, &pos, &page, &usage)) { + if (!get_next_hid_usage(rpt_desc.value, rpt_desc.size, &pos, &page, &usage)) { cur_dev->usage_page = page; cur_dev->usage = usage; } From 728c1e18e759aa9123702990eb52199ed48a0545 Mon Sep 17 00:00:00 2001 From: Jacob Alexander Date: Thu, 19 Nov 2020 10:22:37 -0800 Subject: [PATCH 09/11] Update linux/hid.c Co-authored-by: Ihor Dutchak --- linux/hid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linux/hid.c b/linux/hid.c index 06f2e18af..ab2d1aa58 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -791,7 +791,7 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, cur_dev->path = strdup(dev_path); cur_dev->vendor_id = dev_vid; cur_dev->product_id = dev_pid; - cur_dev->serial_number = wcsdup(prev_dev->serial_number); + cur_dev->serial_number = prev_dev->serial_number? wcsdup(prev_dev->serial_number): NULL; cur_dev->release_number = prev_dev->release_number; cur_dev->interface_number = prev_dev->interface_number; cur_dev->manufacturer_string = prev_dev->manufacturer_string? wcsdup(prev_dev->manufacturer_string): NULL; From a3f0776a6eb1a60fcdd903b3e6cf62e5ea30dab2 Mon Sep 17 00:00:00 2001 From: Ihor Dutchak Date: Thu, 19 Nov 2020 20:26:51 +0200 Subject: [PATCH 10/11] Update linux/hid.c --- linux/hid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linux/hid.c b/linux/hid.c index ab2d1aa58..778d90bb2 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -756,7 +756,7 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, } /* Construct /device/report_descriptor */ - int rpt_path_len = strlen(sysfs_path) + 25 + 1; + size_t rpt_path_len = strlen(sysfs_path) + 25 + 1; char *rpt_path = (char *)calloc(1, rpt_path_len); snprintf(rpt_path, rpt_path_len, "%s/device/report_descriptor", sysfs_path); From b62e82b865e5b87a6bd244a16d49152b7ac7d826 Mon Sep 17 00:00:00 2001 From: Ihor Dutchak <> Date: Mon, 23 Nov 2020 20:47:18 +0200 Subject: [PATCH 11/11] make separate get_hid_report_descriptor_from_sysfs --- linux/hid.c | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/linux/hid.c b/linux/hid.c index 778d90bb2..2f5b1323e 100644 --- a/linux/hid.c +++ b/linux/hid.c @@ -365,12 +365,13 @@ static int get_next_hid_usage(__u8 *report_descriptor, __u32 size, unsigned int * Retrieves the hidraw report descriptor from a file. * When using this form, /device/report_descriptor, elevated priviledges are not required. */ -static int get_hid_report_descriptor(char *rpt_path, struct hidraw_report_descriptor *rpt_desc) +static int get_hid_report_descriptor(const char *rpt_path, struct hidraw_report_descriptor *rpt_desc) { - int res, rpt_handle; + int rpt_handle; + ssize_t res; rpt_handle = open(rpt_path, O_RDONLY); - if (rpt_handle == -1) { + if (rpt_handle < 0) { register_global_error_format("open failed (%s): %s", rpt_path, strerror(errno)); return -1; } @@ -383,12 +384,26 @@ static int get_hid_report_descriptor(char *rpt_path, struct hidraw_report_descri */ memset(rpt_desc, 0x0, sizeof(*rpt_desc)); res = read(rpt_handle, rpt_desc->value, HID_MAX_DESCRIPTOR_SIZE); - if (res == -1) { + if (res < 0) { register_global_error_format("read failed (%s): %s", rpt_path, strerror(errno)); } - rpt_desc->size = res; + rpt_desc->size = (__u32) res; close(rpt_handle); + return (int) res; +} + +static int get_hid_report_descriptor_from_sysfs(const char *sysfs_path, struct hidraw_report_descriptor *rpt_desc) +{ + int res = -1; + /* Construct /device/report_descriptor */ + size_t rpt_path_len = strlen(sysfs_path) + 25 + 1; + char* rpt_path = (char*) calloc(1, rpt_path_len); + snprintf(rpt_path, rpt_path_len, "%s/device/report_descriptor", sysfs_path); + + res = get_hid_report_descriptor(rpt_path, rpt_desc); + free(rpt_path); + return res; } @@ -623,6 +638,7 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, char *product_name_utf8 = NULL; int bus_type; int result; + struct hidraw_report_descriptor report_desc; /* Get the filename of the /sys entry for the device and create a udev_device object (dev) representing it */ @@ -755,23 +771,16 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, break; } - /* Construct /device/report_descriptor */ - size_t rpt_path_len = strlen(sysfs_path) + 25 + 1; - char *rpt_path = (char *)calloc(1, rpt_path_len); - snprintf(rpt_path, rpt_path_len, "%s/device/report_descriptor", sysfs_path); - /* Usage Page and Usage */ - int res; - struct hidraw_report_descriptor rpt_desc; - res = get_hid_report_descriptor(rpt_path, &rpt_desc); - if (res >= 0) { + result = get_hid_report_descriptor_from_sysfs(sysfs_path, &report_desc); + if (result >= 0) { unsigned short page = 0, usage = 0; unsigned int pos = 0; /* * Parse the first usage and usage page * out of the report descriptor. */ - if (!get_next_hid_usage(rpt_desc.value, rpt_desc.size, &pos, &page, &usage)) { + if (!get_next_hid_usage(report_desc.value, report_desc.size, &pos, &page, &usage)) { cur_dev->usage_page = page; cur_dev->usage = usage; } @@ -780,7 +789,7 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, * Parse any additional usage and usage pages * out of the report descriptor. */ - while (!get_next_hid_usage(rpt_desc.value, rpt_desc.size, &pos, &page, &usage)) { + while (!get_next_hid_usage(report_desc.value, report_desc.size, &pos, &page, &usage)) { /* Create new record for additional usage pairs */ tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); cur_dev->next = tmp; @@ -800,7 +809,6 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, cur_dev->usage = usage; } } - free(rpt_path); } next: