Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 142 additions & 19 deletions linux/hid.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <stdlib.h>
#include <locale.h>
#include <errno.h>
#include <stdint.h>

/* Unix */
#include <unistd.h>
Expand Down Expand Up @@ -169,8 +170,6 @@ 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.
Expand Down Expand Up @@ -214,6 +213,126 @@ 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 uint32_t get_bytes(uint8_t *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_usage(uint8_t *report_descriptor, size_t size,
unsigned short *usage_page, unsigned short *usage)
{
int i = 0;
int size_code;
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;

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;
}

if (key_cmd == 0x4) {
*usage_page = get_bytes(report_descriptor, size, data_len, i);
usage_page_found = 1;
//printf("Usage Page: %x\n", (uint32_t)*usage_page);
}
if (key_cmd == 0x8) {
*usage = get_bytes(report_descriptor, size, data_len, i);
usage_found = 1;
//printf("Usage: %x\n", (uint32_t)*usage);
}

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 */
}

static int get_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) {
perror("HIDIOCGRDESCSIZE");
return res;
}

/* Get Report Descriptor */
rpt_desc->size = desc_size;
res = ioctl(device_handle, HIDIOCGRDESC, rpt_desc);
if (res < 0)
perror("HIDIOCGRDESC");

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.
Expand Down Expand Up @@ -538,6 +657,23 @@ 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_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_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;
Expand Down Expand Up @@ -657,26 +793,13 @@ 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_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,
rpt_desc.size);
uses_numbered_reports(rpt_desc.value, rpt_desc.size);
}

return dev;
Expand Down