Skip to content

Instantly share code, notes, and snippets.

@tschiemer
Created March 17, 2021 14:16
Show Gist options
  • Save tschiemer/1b6d4f7fb0a210ae7acb9ae380878de1 to your computer and use it in GitHub Desktop.
Save tschiemer/1b6d4f7fb0a210ae7acb9ae380878de1 to your computer and use it in GitHub Desktop.
quick and dirty method of listing HID devices through libusb with code excerpts from hidapi
/*
* libusb example program to list devices on the bus
* Copyright © 2007 Daniel Drake <dsd@gentoo.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "libusb.h"
#include <stdbool.h>
#include <stdio.h>
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)
{
unsigned int i = 0;
int size_code;
int data_len, key_size;
int usage_found = 0, usage_page_found = 0;
int depth = 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;
}
printf("key_cmd: %02hhx ", key_cmd);
for(int d = 0; d < depth; d++){
printf(" ");
}
#define ITEM_PREFIX 0b11111100
#define ITEM_USAGE 0b00000100
#define ITEM_LOGMIN 0b00010100
#define ITEM_LOGMAX 0b00011000
#define ITEM_PHYMIN 0b00110100
#define ITEM_PHYMAX 0b01000100
#define ITEM_EXP 0b01010100
#define ITEM_UNIT 0b01100100
#define ITEM_SIZE 0b01110100
#define ITEM_REPID 0b10000100
#define ITEM_REPCNT 0b10010100
#define ITEM_PUSH 0b10100100
#define ITEM_POP 0b10110100
#define ITEM_INPUT 0b10000000
#define ITEM_OUTPUT 0b10010000
#define ITEM_FEATURE 0b10110000
#define ITEM_COLLECT 0b10100000
#define ITEM_EOC 0b11000000
switch(key & ITEM_PREFIX){
case ITEM_INPUT:
printf("[input] ");
break;
case ITEM_OUTPUT:
printf("[output] ");
break;
case ITEM_FEATURE:
printf("[feature] ");
break;
case ITEM_COLLECT:
printf("[collection start] ");
depth++;
break;
case ITEM_EOC:
printf("\b\b[collection end] ");
depth--;
break;
case ITEM_USAGE:
printf("[usage page] ");
break;
case ITEM_LOGMIN:
printf("[logic min] ");
break;
case ITEM_LOGMAX:
printf("[logic max] ");
break;
case ITEM_PHYMIN:
printf("[phy min] ");
break;
case ITEM_PHYMAX:
printf("[phy max] ");
break;
case ITEM_EXP:
printf("[unit exp] ");
break;
case ITEM_UNIT:
printf("[unit] ");
break;
case ITEM_SIZE:
printf("[size] ");
break;
case ITEM_REPID:
printf("[report id] ");
break;
case ITEM_REPCNT:
printf("[report count] ");
break;
case ITEM_PUSH:
printf("[push] ");
break;
case ITEM_POP:
printf("[pop] ");
break;
default:
printf("[RESERVED] ");
break;
}
printf("data_len = %d size = %d ", data_len, key_size);
if (key_cmd == 0x4 && usage_page_found == 0) {
*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_found == 0) {
*usage = get_bytes(report_descriptor, size, data_len, i);
usage_found = 1;
//printf("Usage: %x\n", (uint32_t)*usage);
}
for (uint32_t o = i; o < i + data_len + key_size; o++){
printf("%02x ", report_descriptor[o]);
}
/* Skip over this key and it's associated data */
i += data_len + key_size;
printf("\n");
}
if (usage_page_found && usage_found)
return 0; /* success */
return -1; /* failure */
}
static void print_devs(libusb_device **devs)
{
libusb_device *dev;
int i = 0, j = 0;
// uint8_t path[8];
while ((dev = devs[i++]) != NULL) {
struct libusb_device_descriptor desc;
int r = libusb_get_device_descriptor(dev, &desc);
if (r < 0) {
fprintf(stderr, "failed to get device descriptor");
return;
}
if (desc.bDeviceClass != 0 || desc.bDeviceSubClass != 0 || desc.bDeviceProtocol != 0 || desc.bNumConfigurations != 1){
continue;
}
// char manufacturer_string[256];
// libusb_get_string_descriptor_ascii(dev, desc.iManufacturer, manufacturer_string, sizeof(manufacturer_string));
printf("%04x:%04x (bus %d, device %d) class = %d sub = %d proto = %d num_configs %d\n",
desc.idVendor, desc.idProduct,
libusb_get_bus_number(dev), libusb_get_device_address(dev),
// manufacturer_string,
desc.bDeviceClass, desc.bDeviceSubClass, desc.bDeviceProtocol,
desc.bNumConfigurations
);
// r = libusb_get_port_numbers(dev, path, sizeof(path));
// if (r > 0) {
// // printf(" path: %d", path[0]);
// for (j = 1; j < r; j++){}
// // printf(".%d", path[j]);
// }
struct libusb_config_descriptor *config;
for (j = 0; j < desc.bNumConfigurations; j++)
{
// r = libusb_get_active_config_descriptor(dev, &config);
// if (r <= 0){
r = libusb_get_config_descriptor(dev, 0, &config);
// }
if (r <0 ){
fprintf(stderr, "\t failed to get config %d\n", j);
} else {
// if (config->bNumInterfaces != 1){
// printf("SKIPPING\n");
// } else {
printf(" cfg %d bNumInterfaces = %d, extra_length = %d\n", 0, config->bNumInterfaces, config->extra_length);
for(int k = 0; k < config->bNumInterfaces; k++){
printf("\t if %d / num_altsetting = %d\n", k, config->interface[k].num_altsetting);
for (int l = 0; l < config->interface[k].num_altsetting; l++){
printf("\t\t ifnum = %d, class = %d, subbclass = %d, protocol = %d, extra_length = %d ", config->interface[k].altsetting[l].bInterfaceNumber, config->interface[k].altsetting[l].bInterfaceClass, config->interface[k].altsetting[l].bInterfaceSubClass, config->interface[k].altsetting[l].bInterfaceProtocol, config->interface[k].altsetting[l].extra_length);
if (config->interface[k].altsetting[l].extra_length){
printf("[%02x", config->interface[k].altsetting[l].extra[0]);
for (int m = 1; m < config->interface[k].altsetting[l].extra_length; m++){
printf(" %02x", config->interface[k].altsetting[l].extra[m]);
}
printf("]");
}
printf("\n");
libusb_device_handle *handle;
r = libusb_open(dev, &handle);
if (r < 0){
fprintf(stderr, "failed to open dev\n");
} else {
uint8_t interface_num = config->interface[k].altsetting[l].bInterfaceNumber;
// bool detached = false;
//
// r = libusb_kernel_driver_active(handle, interface_num);
// if (r == 1) {
// r = libusb_detach_kernel_driver(handle, interface_num);
// if (r < 0){
// fprintf(stderr, "Couldn't detach kernel driver, even though a kernel driver was attached.");
// } else {
// detached = true;
// }
// }
// r = libusb_claim_interface(handle, interface_num);
// if (r >= 0)
{
// /* Get the HID Report Descriptor. */
uint8_t data[256];
r = libusb_control_transfer(handle, LIBUSB_ENDPOINT_IN|LIBUSB_RECIPIENT_INTERFACE, LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_REPORT << 8)|interface_num, 0, data, sizeof(data), 5000);
if (r >= 0) {
unsigned short page=0, usage=0;
/* Parse the usage and usage page
out of the report descriptor. */
get_usage(data, r, &page, &usage);
printf("\t\t usage (page) = %d (%d)\n", usage, page);
printf("\t\t");
for ( int rt = 0; rt < r; rt++){
printf("%02x ", data[rt]);
}
printf("\n");
}
else {
fprintf(stderr, "libusb_control_transfer() for getting the HID report failed with %d\n", r);
}
/* Release the interface */
// r = libusb_release_interface(handle, interface_num);
// if (r < 0){
// fprintf(stderr,"Can't release the interface.\n");
// }
}
// else {
// fprintf(stderr,"Can't claim interface %d\n", r);
// }
// if (detached) {
// r = libusb_attach_kernel_driver(handle, interface_num);
// if (r < 0)
// fprintf(stderr,"Couldn't re-attach kernel driver.\n");
// }
libusb_close(handle);
}
printf("\t\t bNumEndpoints = %d\n", config->interface[k].altsetting[l].bNumEndpoints);
for(int e = 0; e < config->interface[k].altsetting[l].bNumEndpoints; e++){
printf("\t\t\t extra_length = %d\n", config->interface[k].altsetting[l].endpoint[e].extra_length);
}
}
// printf("\t\t if.class = %d, subbclass = %d\n", config->interface[k].bInterfaceClass, config->interface[k].bInterfaceSubClass);
}
// }
libusb_free_config_descriptor(config);
}
}
printf("\n");
}
}
int main(void)
{
libusb_device **devs;
int r;
ssize_t cnt;
r = libusb_init(NULL);
if (r < 0)
return r;
cnt = libusb_get_device_list(NULL, &devs);
if (cnt < 0){
libusb_exit(NULL);
return (int) cnt;
}
print_devs(devs);
libusb_free_device_list(devs, 1);
libusb_exit(NULL);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment