From 17d8479d980ebd37707c03e4d9e86aa0438f0ab9 Mon Sep 17 00:00:00 2001 From: Steven Noonan Date: Thu, 22 Apr 2021 15:44:01 -0700 Subject: [PATCH] hidapi/libusb: maintain in-memory cache of vendor/product strings The get_usb_string call is rather expensive on some USB devices, so we cache the vendor/product strings for future lookups (e.g. when hid_enumerate is invoked again later). This way, we only need to ask libusb for strings for devices we haven't seen since before we started. Signed-off-by: Steven Noonan Signed-off-by: Sam Lantinga --- src/hidapi/libusb/hid.c | 111 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 105 insertions(+), 6 deletions(-) diff --git a/src/hidapi/libusb/hid.c b/src/hidapi/libusb/hid.c index 3fd95e061..1175da99c 100644 --- a/src/hidapi/libusb/hid.c +++ b/src/hidapi/libusb/hid.c @@ -440,6 +440,94 @@ err: return str; } +struct usb_string_cache_entry { + uint16_t vid; + uint16_t pid; + wchar_t *vendor; + wchar_t *product; +}; + +static struct usb_string_cache_entry *usb_string_cache = NULL; +static size_t usb_string_cache_size = 0; +static size_t usb_string_cache_insert_pos = 0; + +static int usb_string_cache_grow() +{ + struct usb_string_cache_entry *new_cache; + size_t allocSize; + size_t new_cache_size; + + new_cache_size = usb_string_cache_size + 8; + allocSize = sizeof(struct usb_string_cache_entry) * new_cache_size; + new_cache = (struct usb_string_cache_entry *)realloc(usb_string_cache, allocSize); + if (!new_cache) + return -1; + + usb_string_cache = new_cache; + usb_string_cache_size = new_cache_size; + + return 0; +} + +static void usb_string_cache_destroy() +{ + size_t i; + for (i = 0; i < usb_string_cache_insert_pos; i++) { + free(usb_string_cache[i].vendor); + free(usb_string_cache[i].product); + } + free(usb_string_cache); + + usb_string_cache = NULL; + usb_string_cache_size = 0; +} + +static struct usb_string_cache_entry *usb_string_cache_insert() +{ + struct usb_string_cache_entry *new_entry = NULL; + if (usb_string_cache_insert_pos >= usb_string_cache_size) { + if (usb_string_cache_grow() < 0) + return NULL; + } + new_entry = &usb_string_cache[usb_string_cache_insert_pos]; + usb_string_cache_insert_pos++; + return new_entry; +} + +static const struct usb_string_cache_entry *usb_string_cache_find(struct libusb_device_descriptor *desc, struct libusb_device_handle *handle) +{ + struct usb_string_cache_entry *entry = NULL; + size_t i; + + /* Search for existing string cache entry */ + for (i = 0; i < usb_string_cache_insert_pos; i++) { + entry = &usb_string_cache[i]; + if (entry->vid != desc->idVendor) + continue; + if (entry->pid != desc->idProduct) + continue; + return entry; + } + + /* Not found, create one. */ + entry = usb_string_cache_insert(); + if (!entry) + return NULL; + + entry->vid = desc->idVendor; + entry->pid = desc->idProduct; + if (desc->iManufacturer > 0) + entry->vendor = get_usb_string(handle, desc->iManufacturer); + else + entry->vendor = NULL; + if (desc->iProduct > 0) + entry->product = get_usb_string(handle, desc->iProduct); + else + entry->product = NULL; + + return entry; +} + static char *make_path(libusb_device *dev, int interface_number) { char str[64]; @@ -473,6 +561,8 @@ int HID_API_EXPORT hid_init(void) int HID_API_EXPORT hid_exit(void) { + usb_string_cache_destroy(); + if (usb_context) { libusb_exit(usb_context); usb_context = NULL; @@ -617,6 +707,7 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, if (res >= 0) { struct hid_device_info *tmp; + const struct usb_string_cache_entry *string_cache; /* VID/PID match. Create the record. */ tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); @@ -638,12 +729,20 @@ struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, get_usb_string(handle, desc.iSerialNumber); /* Manufacturer and Product strings */ - if (desc.iManufacturer > 0) - cur_dev->manufacturer_string = - get_usb_string(handle, desc.iManufacturer); - if (desc.iProduct > 0) - cur_dev->product_string = - get_usb_string(handle, desc.iProduct); + if (dev_vid && dev_pid) { + string_cache = usb_string_cache_find(&desc, handle); + if (string_cache) { + cur_dev->manufacturer_string = wcsdup(string_cache->vendor); + cur_dev->product_string = wcsdup(string_cache->product); + } + } else { + if (desc.iManufacturer > 0) + cur_dev->manufacturer_string = + get_usb_string(handle, desc.iManufacturer); + if (desc.iProduct > 0) + cur_dev->product_string = + get_usb_string(handle, desc.iProduct); + } #ifdef INVASIVE_GET_USAGE {