From fbb23a3cb06b0bc528c4cf9345646f7e82756949 Mon Sep 17 00:00:00 2001 From: Nikolai Kondrashov Date: Wed, 17 Nov 2010 20:14:41 +0300 Subject: Add multi-device dumping with limiting Add multi-device dumping. Add independent limiting by bus number, device address, vendor, product and interface number. --- include/uhd/Makefile.am | 2 + include/uhd/dev.h | 76 +++++++ include/uhd/dev_list.h | 108 ++++++++++ include/uhd/iface.h | 14 +- include/uhd/iface_list.h | 20 +- lib/Makefile.am | 2 + lib/dev.c | 89 ++++++++ lib/dev_list.c | 151 +++++++++++++ lib/iface.c | 46 ++-- lib/iface_list.c | 115 +++++----- src/usbhid-dump.c | 538 +++++++++++++++++++++++++++++++---------------- 11 files changed, 901 insertions(+), 260 deletions(-) create mode 100644 include/uhd/dev.h create mode 100644 include/uhd/dev_list.h create mode 100644 lib/dev.c create mode 100644 lib/dev_list.c diff --git a/include/uhd/Makefile.am b/include/uhd/Makefile.am index 7a84851..71cdc11 100644 --- a/include/uhd/Makefile.am +++ b/include/uhd/Makefile.am @@ -17,6 +17,8 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA noinst_HEADERS = \ + dev.h \ + dev_list.h \ iface.h \ iface_list.h \ libusb.h \ diff --git a/include/uhd/dev.h b/include/uhd/dev.h new file mode 100644 index 0000000..21775db --- /dev/null +++ b/include/uhd/dev.h @@ -0,0 +1,76 @@ +/** @file + * @brief usbhid-dump - device + * + * Copyright (C) 2010 Nikolai Kondrashov + * + * This file is part of usbhid-dump. + * + * Usbhid-dump is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Usbhid-dump 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with usbhid-dump; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * @author Nikolai Kondrashov + * + * @(#) $Id$ + */ + +#ifndef __UHD_DEV_H__ +#define __UHD_DEV_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** usbhid-dump device */ +typedef struct uhd_dev uhd_dev; + +struct uhd_dev { + uhd_dev *next; /**< Next device in the list */ + libusb_device_handle *handle; /**< Handle */ +}; + +/** + * Check if a device is valid. + * + * @param dev Device to check. + * + * @return True if the device is valid, false otherwise. + */ +extern bool uhd_dev_valid(const uhd_dev *dev); + +/** + * Open a device. + * + * @param lusb_dev Libusb device. + * @param pdev Location for the opened device pointer. + * + * @return Libusb error code. + */ +extern enum libusb_error uhd_dev_open(libusb_device *lusb_dev, + uhd_dev **pdev); + +/** + * Close a device. + * + * @param dev The device to close. + */ +extern void uhd_dev_close(uhd_dev *dev); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __UHD_DEV_H__ */ diff --git a/include/uhd/dev_list.h b/include/uhd/dev_list.h new file mode 100644 index 0000000..d75c613 --- /dev/null +++ b/include/uhd/dev_list.h @@ -0,0 +1,108 @@ +/** @file + * @brief usbhid-dump - device list + * + * Copyright (C) 2010 Nikolai Kondrashov + * + * This file is part of usbhid-dump. + * + * Usbhid-dump is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Usbhid-dump 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with usbhid-dump; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * @author Nikolai Kondrashov + * + * @(#) $Id$ + */ + +#ifndef __UHD_DEV_LIST_H__ +#define __UHD_DEV_LIST_H__ + +#include "uhd/dev.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Check if a device list is valid. + * + * @param list Device list to check. + * + * @return True if the device list is valid, false otherwise. + */ +extern bool uhd_dev_list_valid(const uhd_dev *list); + +/** + * Check if a device list is empty. + * + * @param list Device list to check. + * + * @return True if the device list is empty, false otherwise. + */ +static inline bool +uhd_dev_list_empty(const uhd_dev *list) +{ + return list == NULL; +} + +/** + * Calculate length of a device list. + * + * @param list The list to calculate length of. + * + * @return The list length. + */ +extern size_t uhd_dev_list_len(const uhd_dev *list); + +/** + * Close every device in a device list. + * + * @param list The device list to close. + */ +extern void uhd_dev_list_close(uhd_dev *list); + +/** + * Iterate over a device list. + * + * @param _dev Loop device variable. + * @param _list Device list to iterate over. + */ +#define UHD_DEV_LIST_FOR_EACH(_dev, _list) \ + for (_dev = _list; _dev != NULL; _dev = _dev->next) + +/** + * Open a list of devices optionally matching bus number/device address and + * vendor/product IDs. + * + * @param ctx Libusb context. + * @param bus_num Bus number, or 0 for any bus. + * @param dev_addr Device address, or 0 for any address. + * @param vendor Vendor ID, or 0 for any vendor. + * @param product Product ID, or 0 for any product. + * @param plist Location for the resulting device list head; could be + * NULL. + * + * @return Libusb error code. + */ +extern enum libusb_error uhd_dev_list_open(libusb_context *ctx, + uint8_t bus_num, + uint8_t dev_addr, + uint16_t vendor, + uint16_t product, + uhd_dev **plist); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __UHD_DEV_LIST_H__ */ diff --git a/include/uhd/iface.h b/include/uhd/iface.h index b87e6db..d6bd67d 100644 --- a/include/uhd/iface.h +++ b/include/uhd/iface.h @@ -27,8 +27,7 @@ #ifndef __UHD_IFACE_H__ #define __UHD_IFACE_H__ -#include -#include +#include "uhd/dev.h" #ifdef __cplusplus extern "C" { @@ -39,8 +38,9 @@ typedef struct uhd_iface uhd_iface; struct uhd_iface { uhd_iface *next; - libusb_device_handle *handle; /**< Device handle */ + const uhd_dev *dev; /**< Device */ uint8_t number; /**< Interface number */ + char addr_str[12]; /**< Address string */ uint8_t int_in_ep_addr; /**< Interrupt IN EP address */ uint16_t int_in_ep_maxp; /**< Interrupt IN EP maximum packet size */ @@ -79,10 +79,10 @@ extern bool uhd_iface_valid(const uhd_iface *iface); * * @return New interface or NULL, if failed to allocate. */ -extern uhd_iface *uhd_iface_new(libusb_device_handle *handle, - uint8_t number, - uint8_t int_in_ep_addr, - uint16_t int_in_ep_maxp); +extern uhd_iface *uhd_iface_new(const uhd_dev *dev, + uint8_t number, + uint8_t int_in_ep_addr, + uint16_t int_in_ep_maxp); /** * Free an interface. diff --git a/include/uhd/iface_list.h b/include/uhd/iface_list.h index c93a3da..17ad778 100644 --- a/include/uhd/iface_list.h +++ b/include/uhd/iface_list.h @@ -27,6 +27,7 @@ #ifndef __UHD_IFACE_LIST_H__ #define __UHD_IFACE_LIST_H__ +#include "uhd/dev_list.h" #include "uhd/iface.h" #ifdef __cplusplus @@ -81,29 +82,26 @@ extern void uhd_iface_list_free(uhd_iface *list); for (_iface = _list; _iface != NULL; _iface = _iface->next) /** - * Fetch a list of HID interfaces from a device. + * Fetch a list of HID interfaces from a device list. * - * @param handle The device handle to fetch interface list from. + * @param dev_list The device list to fetch interface list from. * @param plist Location for the resulting list head; could be NULL. * * @return Libusb error code. */ -enum libusb_error -uhd_iface_list_new_from_dev(libusb_device_handle *handle, - uhd_iface **plist); +extern enum libusb_error uhd_iface_list_new(uhd_dev *dev_list, + uhd_iface **plist); /** - * Filter an interface list by an optional interface number, resulting - * either in an empty, a single-interface, or an unmodified list. + * Filter an interface list by an interface number. * * @param plist The original list head. - * @param number The interface number to match against, or a negative - * integer meaning there is no restriction. + * @param number The interface number to match against. * - * @return The resulting list head + * @return The resulting list head. */ extern uhd_iface *uhd_iface_list_fltr_by_num(uhd_iface *list, - int number); + uint8_t number); #ifdef __cplusplus } /* extern "C" */ diff --git a/lib/Makefile.am b/lib/Makefile.am index 5422448..b9ce040 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -19,6 +19,8 @@ noinst_LTLIBRARIES = libuhd.la libuhd_la_SOURCES = \ + dev.c \ + dev_list.c \ iface.c \ iface_list.c \ libusb.c \ diff --git a/lib/dev.c b/lib/dev.c new file mode 100644 index 0000000..2e0a487 --- /dev/null +++ b/lib/dev.c @@ -0,0 +1,89 @@ +/** @file + * @brief usbhid-dump - device + * + * Copyright (C) 2010 Nikolai Kondrashov + * + * This file is part of usbhid-dump. + * + * Usbhid-dump is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Usbhid-dump 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with usbhid-dump; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * @author Nikolai Kondrashov + * + * @(#) $Id$ + */ + +#include "uhd/dev.h" +#include +#include + +bool +uhd_dev_valid(const uhd_dev *dev) +{ + return dev != NULL && + dev->handle != NULL; +} + + +enum libusb_error +uhd_dev_open(libusb_device *lusb_dev, + uhd_dev **pdev) +{ + enum libusb_error err; + uhd_dev *dev; + + assert(lusb_dev != NULL); + + dev = malloc(sizeof(*dev)); + if (dev == NULL) + return LIBUSB_ERROR_NO_MEM; + + dev->next = NULL; + + err = libusb_open(lusb_dev, &dev->handle); + if (err != LIBUSB_SUCCESS) + { + free(dev); + return err; + } + + assert(uhd_dev_valid(dev)); + + if (pdev != NULL) + *pdev = dev; + else + { + libusb_close(dev->handle); + free(dev); + } + + return LIBUSB_SUCCESS; +} + + +void +uhd_dev_close(uhd_dev *dev) +{ + if (dev == NULL) + return; + + assert(uhd_dev_valid(dev)); + + libusb_close(dev->handle); + dev->handle = NULL; + + free(dev); +} + + diff --git a/lib/dev_list.c b/lib/dev_list.c new file mode 100644 index 0000000..3318b4f --- /dev/null +++ b/lib/dev_list.c @@ -0,0 +1,151 @@ +/** @file + * @brief usbhid-dump - device list + * + * Copyright (C) 2010 Nikolai Kondrashov + * + * This file is part of usbhid-dump. + * + * Usbhid-dump is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Usbhid-dump 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with usbhid-dump; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * @author Nikolai Kondrashov + * + * @(#) $Id$ + */ + +#include "uhd/dev_list.h" +#include +#include +#include + +bool +uhd_dev_list_valid(const uhd_dev *list) +{ + UHD_DEV_LIST_FOR_EACH(list, list) + if (!uhd_dev_valid(list)) + return false; + + return true; +} + + +size_t +uhd_dev_list_len(const uhd_dev *list) +{ + size_t len = 0; + + UHD_DEV_LIST_FOR_EACH(list, list) + len++; + + return len; +} + + +void +uhd_dev_list_close(uhd_dev *list) +{ + uhd_dev *next; + + for (; list != NULL; list = next) + { + next = list->next; + uhd_dev_close(list); + } +} + + +enum libusb_error +uhd_dev_list_open(libusb_context *ctx, + uint8_t bus_num, uint8_t dev_addr, + uint16_t vendor, uint16_t product, + uhd_dev **plist) +{ + enum libusb_error err = LIBUSB_ERROR_OTHER; + libusb_device **lusb_list = NULL; + ssize_t num; + ssize_t idx; + libusb_device *lusb_dev; + struct libusb_device_descriptor desc; + uhd_dev *list = NULL; + uhd_dev *dev; + + assert(ctx != NULL); + + /* Retrieve libusb device list */ + num = libusb_get_device_list(ctx, &lusb_list); + if (num == LIBUSB_ERROR_NO_MEM) + { + err = num; + goto cleanup; + } + + /* Find and open the devices */ + for (idx = 0; idx < num; idx++) + { + lusb_dev = lusb_list[idx]; + + /* Skip devices not matching bus_num/dev_addr mask */ + if ((bus_num != 0 && libusb_get_bus_number(lusb_dev) != bus_num) || + (dev_addr != 0 && libusb_get_device_address(lusb_dev) != dev_addr)) + continue; + + /* Skip devices not matching vendor/product mask */ + if (vendor != 0 || product != 0) + { + err = libusb_get_device_descriptor(lusb_dev, &desc); + if (err != LIBUSB_SUCCESS) + goto cleanup; + + if ((vendor != 0 && vendor != desc.idVendor) || + (product != 0 && product != desc.idProduct)) + continue; + } + + /* Open and append the device to the list */ + err = uhd_dev_open(lusb_dev, &dev); + if (err != LIBUSB_SUCCESS) + goto cleanup; + + dev->next = list; + list = dev; + } + + /* Free the libusb device list freeing unused devices */ + libusb_free_device_list(lusb_list, true); + lusb_list = NULL; + + /* Output device list, if requested */ + assert(uhd_dev_list_valid(list)); + if (plist != NULL) + { + *plist = list; + list = NULL; + } + + /* Done! */ + err = LIBUSB_SUCCESS; + +cleanup: + + /* Close the device list if not output */ + uhd_dev_list_close(list); + + /* Free the libusb device list along with devices */ + if (lusb_list != NULL) + libusb_free_device_list(lusb_list, true); + + return err; +} + + diff --git a/lib/iface.c b/lib/iface.c index 445d902..e84516e 100644 --- a/lib/iface.c +++ b/lib/iface.c @@ -28,29 +28,33 @@ #include #include #include +#include bool uhd_iface_valid(const uhd_iface *iface) { return iface != NULL && - iface->handle != NULL && - iface->number < UINT8_MAX; + uhd_dev_valid(iface->dev) && + iface->number < UINT8_MAX && + strlen(iface->addr_str) == (sizeof(iface->addr_str) - 1); } uhd_iface * -uhd_iface_new(libusb_device_handle *handle, - uint8_t number, - uint8_t int_in_ep_addr, - uint16_t int_in_ep_maxp) +uhd_iface_new(const uhd_dev *dev, + uint8_t number, + uint8_t int_in_ep_addr, + uint16_t int_in_ep_maxp) { - uhd_iface *iface; + uhd_iface *iface; + libusb_device *lusb_dev; + int rc; iface = malloc(sizeof(*iface)); if (iface == NULL) return NULL; iface->next = NULL; - iface->handle = handle; + iface->dev = dev; iface->number = number; iface->int_in_ep_addr = int_in_ep_addr; iface->int_in_ep_maxp = int_in_ep_maxp; @@ -58,6 +62,17 @@ uhd_iface_new(libusb_device_handle *handle, iface->claimed = false; iface->submitted = false; + /* Format address string */ + lusb_dev = libusb_get_device(dev->handle); + rc = snprintf(iface->addr_str, sizeof(iface->addr_str), + "%.3hhu:%.3hhu:%.3hhu", + libusb_get_bus_number(lusb_dev), + libusb_get_device_address(lusb_dev), + number); + assert(rc == (sizeof(iface->addr_str) - 1)); + + assert(uhd_iface_valid(iface)); + return iface; } @@ -81,7 +96,7 @@ uhd_iface_detach(uhd_iface *iface) assert(uhd_iface_valid(iface)); - err = libusb_detach_kernel_driver(iface->handle, iface->number); + err = libusb_detach_kernel_driver(iface->dev->handle, iface->number); if (err == LIBUSB_SUCCESS) iface->detached = true; else if (err != LIBUSB_ERROR_NOT_FOUND) @@ -100,7 +115,8 @@ uhd_iface_attach(uhd_iface *iface) if (iface->detached) { - err = libusb_attach_kernel_driver(iface->handle, iface->number); + err = libusb_attach_kernel_driver(iface->dev->handle, + iface->number); if (err != LIBUSB_SUCCESS) return err; iface->detached = false; @@ -117,7 +133,7 @@ uhd_iface_claim(uhd_iface *iface) assert(uhd_iface_valid(iface)); - err = libusb_claim_interface(iface->handle, iface->number); + err = libusb_claim_interface(iface->dev->handle, iface->number); if (err != LIBUSB_SUCCESS) return err; @@ -134,7 +150,7 @@ uhd_iface_release(uhd_iface *iface) assert(uhd_iface_valid(iface)); - err = libusb_release_interface(iface->handle, iface->number); + err = libusb_release_interface(iface->dev->handle, iface->number); if (err != LIBUSB_SUCCESS) return err; @@ -151,7 +167,7 @@ uhd_iface_clear_halt(uhd_iface *iface) assert(uhd_iface_valid(iface)); - err = libusb_clear_halt(iface->handle, iface->int_in_ep_addr); + err = libusb_clear_halt(iface->dev->handle, iface->int_in_ep_addr); if (err != LIBUSB_SUCCESS) return err; @@ -168,7 +184,7 @@ uhd_iface_set_idle(const uhd_iface *iface, assert(uhd_iface_valid(iface)); - rc = libusb_control_transfer(iface->handle, + rc = libusb_control_transfer(iface->dev->handle, /* host->device, class, interface */ 0x21, /* Set_Idle */ @@ -200,7 +216,7 @@ uhd_iface_set_protocol(const uhd_iface *iface, assert(uhd_iface_valid(iface)); - rc = libusb_control_transfer(iface->handle, + rc = libusb_control_transfer(iface->dev->handle, /* host->device, class, interface */ 0x21, /* Set_Protocol */ diff --git a/lib/iface_list.c b/lib/iface_list.c index d54dc3f..6ac30bd 100644 --- a/lib/iface_list.c +++ b/lib/iface_list.c @@ -66,89 +66,105 @@ uhd_iface_list_free(uhd_iface *list) enum libusb_error -uhd_iface_list_new_from_dev(libusb_device_handle *handle, - uhd_iface **plist) +uhd_iface_list_new(uhd_dev *dev_list, + uhd_iface **plist) { - enum libusb_error err = LIBUSB_ERROR_OTHER; + enum libusb_error err = LIBUSB_ERROR_OTHER; + uhd_dev *dev; struct libusb_config_descriptor *config = NULL; - const struct libusb_interface *libusb_iface; + const struct libusb_interface *lusb_iface; const struct libusb_endpoint_descriptor *ep_list; uint8_t ep_num; const struct libusb_endpoint_descriptor *ep; uhd_iface *list = NULL; - uhd_iface *last = NULL; uhd_iface *iface; - assert(handle != NULL); + assert(uhd_dev_list_valid(dev_list)); - /* Retrieve active configuration descriptor */ - err = libusb_get_active_config_descriptor(libusb_get_device(handle), - &config); - if (err != LIBUSB_SUCCESS) - goto cleanup; - - /* Build the matching interface list */ - for (libusb_iface = config->interface; - libusb_iface - config->interface < config->bNumInterfaces; - libusb_iface++) + UHD_DEV_LIST_FOR_EACH(dev, dev_list) { - if (libusb_iface->num_altsetting != 1 || - libusb_iface->altsetting->bInterfaceClass != LIBUSB_CLASS_HID) - continue; - - ep_list = libusb_iface->altsetting->endpoint; - ep_num = libusb_iface->altsetting->bNumEndpoints; - - for (ep = ep_list; (ep - ep_list) < ep_num; ep++) + /* Retrieve active configuration descriptor */ + err = libusb_get_active_config_descriptor(libusb_get_device(dev->handle), + &config); + if (err != LIBUSB_SUCCESS) + goto cleanup; + + /* + * Build the matching interface list + */ + + /* For each interface */ + for (lusb_iface = config->interface; + lusb_iface - config->interface < config->bNumInterfaces; + lusb_iface++) { - if ((ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) != - LIBUSB_TRANSFER_TYPE_INTERRUPT || - (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) != - LIBUSB_ENDPOINT_IN) + /* Skip interfaces with altsettings and non-HID interfaces */ + if (lusb_iface->num_altsetting != 1 || + lusb_iface->altsetting->bInterfaceClass != LIBUSB_CLASS_HID) continue; - iface = uhd_iface_new( - handle, - libusb_iface->altsetting->bInterfaceNumber, - ep->bEndpointAddress, ep->wMaxPacketSize); - if (iface == NULL) - { - err = LIBUSB_ERROR_NO_MEM; - goto cleanup; - } + /* Retrieve endpoint list */ + ep_list = lusb_iface->altsetting->endpoint; + ep_num = lusb_iface->altsetting->bNumEndpoints; - if (last == NULL) + /* For each endpoint */ + for (ep = ep_list; (ep - ep_list) < ep_num; ep++) + { + /* Skip non-interrupt and non-in endpoints */ + if ((ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) != + LIBUSB_TRANSFER_TYPE_INTERRUPT || + (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) != + LIBUSB_ENDPOINT_IN) + continue; + + /* Create the interface */ + iface = uhd_iface_new( + dev, + lusb_iface->altsetting->bInterfaceNumber, + ep->bEndpointAddress, ep->wMaxPacketSize); + if (iface == NULL) + { + err = LIBUSB_ERROR_NO_MEM; + goto cleanup; + } + + /* Add the interface */ + iface->next = list; list = iface; - else - last->next = iface; - last = iface; - break; + break; + } } + + /* Free the config descriptor */ + libusb_free_config_descriptor(config); + config = NULL; } /* Output the resulting list, if requested */ + assert(uhd_iface_list_valid(list)); if (plist != NULL) { *plist = list; list = NULL; } + /* Done! */ + err = LIBUSB_SUCCESS; + cleanup: + libusb_free_config_descriptor(config); uhd_iface_list_free(list); - if (config != NULL) - libusb_free_config_descriptor(config); - return err; } uhd_iface * uhd_iface_list_fltr_by_num(uhd_iface *list, - int number) + uint8_t number) { uhd_iface *prev; uhd_iface *iface; @@ -157,13 +173,10 @@ uhd_iface_list_fltr_by_num(uhd_iface *list, assert(uhd_iface_list_valid(list)); assert(number < UINT8_MAX); - if (number < 0) - return list; - for (prev = NULL, iface = list; iface != NULL; iface = next) { next = iface->next; - if (iface->number == (uint8_t)number) + if (iface->number == number) prev = iface; else { @@ -171,7 +184,7 @@ uhd_iface_list_fltr_by_num(uhd_iface *list, list = next; else prev->next = next; - free(iface); + uhd_iface_free(iface); } } diff --git a/src/usbhid-dump.c b/src/usbhid-dump.c index 466c874..101ad67 100644 --- a/src/usbhid-dump.c +++ b/src/usbhid-dump.c @@ -1,6 +1,5 @@ /** @file - * @brief usbhid-dump - entry point - * + * @brief usbhid-dump - entry point * * Copyright (C) 2010 Nikolai Kondrashov * * This file is part of usbhid-dump. @@ -49,6 +48,17 @@ */ #define MAX_DESCRIPTOR_SIZE 4096 +/** Wildcard bus number */ +#define BUS_NUM_ANY 0 +/** Wildcard device address */ +#define DEV_ADDR_ANY 0 +/** Wildcard vendor ID */ +#define VID_ANY 0 +/** Wildcard product ID */ +#define PID_ANY 0 +/** Wildcard interface number */ +#define IFACE_NUM_ANY UINT8_MAX + /** * USB I/O timeout */ @@ -122,61 +132,11 @@ stream_resume_sighandler(int signum) /**< "Stream feedback" flag - non-zero if feedback is enabled */ static volatile sig_atomic_t stream_feedback = 0; -static bool -usage(FILE *stream, const char *progname) -{ - return - fprintf( - stream, -"Usage: %s [OPTION]... [if]\n" -"Dump a USB device HID report descriptor(s) and/or stream(s)." -"\n" -"Arguments:\n" -" bus bus number (1-255)\n" -" dev device address (1-255)\n" -" if interface number (0-254);\n" -" if omitted, all device HID interfaces\n" -" are dumped\n" -"\n" -"Options:\n" -" -h, --help output this help message and exit\n" -" -v, --version output version information and exit\n" -" -e, --entity=STRING what to dump: either \"descriptor\",\n" -" \"stream\" or \"all\"; value can be\n" -" abbreviated\n" -" -p, --stream-paused start with the stream dump output paused\n" -" -f, --stream-feedback enable stream dumping feedback: for every\n" -" transfer dumped a dot is printed to stderr\n" -"\n" -"Default options: --entity=descriptor\n" -"\n" -"Signals:\n" -" USR1/USR2 pause/resume the stream dump output\n" -"\n", - progname) >= 0; -} - - -static bool -version(FILE *stream) -{ - return - fprintf( - stream, -PACKAGE_STRING "\n" -"Copyright (C) 2010 Nikolai Kondrashov\n" -"License GPLv2+: GNU GPL version 2 or later .\n" -"\n" -"This is free software: you are free to change and redistribute it.\n" -"There is NO WARRANTY, to the extent permitted by law.\n") >= 0; -} - - static void -dump(uint8_t iface_num, - const char *entity, - const uint8_t *ptr, - size_t len) +dump(const uhd_iface *iface, + const char *entity, + const uint8_t *ptr, + size_t len) { static const char xd[] = "0123456789ABCDEF"; static char buf[] = " XX\n"; @@ -186,8 +146,8 @@ dump(uint8_t iface_num, gettimeofday(&tv, NULL); - fprintf(stdout, "%.3hhu:%-16s %20llu.%.6u\n", - iface_num, entity, + fprintf(stdout, "%s:%-16s %12llu.%.6u\n", + iface->addr_str, entity, (unsigned long long int)tv.tv_sec, (unsigned int)tv.tv_usec); @@ -218,7 +178,7 @@ dump_iface_list_descriptor(const uhd_iface *list) UHD_IFACE_LIST_FOR_EACH(iface, list) { - rc = libusb_control_transfer(iface->handle, + rc = libusb_control_transfer(iface->dev->handle, /* See HID spec, 7.1.1 */ 0x81, LIBUSB_REQUEST_GET_DESCRIPTOR, @@ -231,7 +191,7 @@ dump_iface_list_descriptor(const uhd_iface *list) "report descriptor", iface->number); return false; } - dump(iface->number, "DESCRIPTOR", buf, rc); + dump(iface, "DESCRIPTOR", buf, rc); } return true; @@ -258,7 +218,7 @@ dump_iface_list_stream_cb(struct libusb_transfer *transfer) /* Dump the result */ if (!stream_paused) { - dump(iface->number, "STREAM", + dump(iface, "STREAM", transfer->buffer, transfer->actual_length); if (stream_feedback) fputc('.', stderr); @@ -347,13 +307,13 @@ dump_iface_list_stream(libusb_context *ctx, uhd_iface *list) const size_t len = iface->int_in_ep_maxp; /* Allocate the transfer buffer */ - buf = malloc(len); + buf = malloc(len); if (len > 0 && buf == NULL) FAILURE_CLEANUP("allocate a transfer buffer"); /* Initialize the transfer */ libusb_fill_interrupt_transfer(*ptransfer, - iface->handle, iface->int_in_ep_addr, + iface->dev->handle, iface->int_in_ep_addr, buf, len, dump_iface_list_stream_cb, (void *)iface, @@ -488,39 +448,41 @@ cleanup: static int -run(bool dump_descriptor, - bool dump_stream, - uint8_t bus_num, - uint8_t dev_addr, - int iface_num) +run(bool dump_descriptor, + bool dump_stream, + uint8_t bus_num, + uint8_t dev_addr, + uint16_t vid, + uint16_t pid, + int iface_num) { - int result = 1; - enum libusb_error err; - libusb_context *ctx = NULL; - libusb_device_handle *handle = NULL; - uhd_iface *iface_list = NULL; - uhd_iface *iface; - - /* Initialize libusb context */ - err = libusb_init(&ctx); - if (err != LIBUSB_SUCCESS) - LIBUSB_FAILURE_CLEANUP("create libusb context"); + int result = 1; + enum libusb_error err; + libusb_context *ctx = NULL; + uhd_dev *dev_list = NULL; + uhd_iface *iface_list = NULL; + uhd_iface *iface; + + /* Create libusb context */ + LIBUSB_GUARD(libusb_init(&ctx), "create libusb context"); /* Set libusb debug level */ libusb_set_debug(ctx, 3); - /* Find and open the device */ - err = libusb_open_device_with_bus_dev(ctx, bus_num, dev_addr, &handle); - if (err != LIBUSB_SUCCESS) - LIBUSB_FAILURE_CLEANUP("find and open the device"); + /* Open device list */ + LIBUSB_GUARD(uhd_dev_list_open(ctx, bus_num, dev_addr, + vid, pid, &dev_list), + "find and open the devices"); - /* Retrieve the list of HID interfaces from a device */ - err = uhd_iface_list_new_from_dev(handle, &iface_list); - if (err != LIBUSB_SUCCESS) - LIBUSB_FAILURE_CLEANUP("find a HID interface"); + /* Retrieve the list of HID interfaces from the device list */ + LIBUSB_GUARD(uhd_iface_list_new(dev_list, &iface_list), + "find HID interfaces"); /* Filter the interface list by specified interface number */ - iface_list = uhd_iface_list_fltr_by_num(iface_list, iface_num); + if (iface_num != IFACE_NUM_ANY) + iface_list = uhd_iface_list_fltr_by_num(iface_list, iface_num); + + /* Check if there are any interfaces left */ if (uhd_iface_list_empty(iface_list)) ERROR_CLEANUP("No matching HID interfaces"); @@ -553,11 +515,10 @@ cleanup: /* Free the interface list */ uhd_iface_list_free(iface_list); - /* Free the device */ - if (handle != NULL) - libusb_close(handle); + /* Close the device list */ + uhd_dev_list_close(dev_list); - /* Cleanup libusb context, if necessary */ + /* Destroy the libusb context */ if (ctx != NULL) libusb_exit(ctx); @@ -565,73 +526,309 @@ cleanup: } +static bool +parse_number_pair(const char *str, + int base, + long *pn1, + long *pn2) +{ + const char *p; + char *end; + long n1; + long n2; + + assert(str != NULL); + + p = str; + + /* Skip space (prevent strtol doing so) */ + while (isspace(*p)) + p++; + + /* Extract the first number */ + errno = 0; + n1 = strtol(p, &end, base); + if (errno != 0) + return false; + + /* If nothing was read */ + if (end == p) + return false; + + /* Move on */ + p = end; + + /* Skip space */ + while (isspace(*p)) + p++; + + /* If it is the end of string */ + if (*p == '\0') + n2 = 0; + else + { + /* If it is not the number separator */ + if (*p != ':') + return false; + + /* Skip the number separator */ + p++; + + /* Skip space (prevent strtol doing so) */ + while (isspace(*p)) + p++; + + /* Extract the second number */ + errno = 0; + n2 = strtol(p, &end, base); + if (errno != 0) + return false; + /* If nothing was read */ + if (end == p) + return false; + + /* Move on */ + p = end; + + /* Skip space */ + while (isspace(*p)) + p++; + + /* If it is not the end of string */ + if (*p != '\0') + return false; + } + + /* Output the numbers */ + if (pn1 != NULL) + *pn1 = n1; + if (pn2 != NULL) + *pn2 = n2; + + return true; +} + + +static bool +parse_address(const char *str, + uint8_t *pbus_num, + uint8_t *pdev_addr) +{ + long bus_num; + long dev_addr; + + assert(str != NULL); + + if (!parse_number_pair(str, 10, &bus_num, &dev_addr)) + return false; + + if (bus_num < 0 || bus_num > UINT8_MAX || + dev_addr < 0 || dev_addr > UINT8_MAX) + return false; + + if (pbus_num != NULL) + *pbus_num = bus_num; + if (pdev_addr != NULL) + *pdev_addr = dev_addr; + + return true; +} + + +static bool +parse_model(const char *str, + uint16_t *pvid, + uint16_t *ppid) +{ + long vid; + long pid; + + assert(str != NULL); + + if (!parse_number_pair(str, 16, &vid, &pid)) + return false; + + if (vid < 0 || vid > UINT16_MAX || + pid < 0 || pid > UINT16_MAX) + return false; + + if (pvid != NULL) + *pvid = vid; + if (ppid != NULL) + *ppid = pid; + + return true; +} + + +static bool +parse_iface_num(const char *str, + uint8_t *piface_num) +{ + long iface_num; + const char *p; + char *end; + + assert(str != NULL); + + p = str; + + /* Skip space (prevent strtol doing so) */ + while (isspace(*p)) + p++; + + /* Extract interface number */ + errno = 0; + iface_num = strtol(p, &end, 10); + if (errno != 0 || end == p || iface_num < 0 || iface_num > UINT8_MAX) + return false; + + /* Output interface number */ + if (piface_num != NULL) + *piface_num = iface_num; + + return true; +} + + +static bool +version(FILE *stream) +{ + return + fprintf( + stream, +PACKAGE_STRING "\n" +"Copyright (C) 2010 Nikolai Kondrashov\n" +"License GPLv2+: GNU GPL version 2 or later .\n" +"\n" +"This is free software: you are free to change and redistribute it.\n" +"There is NO WARRANTY, to the extent permitted by law.\n") >= 0; +} + + +static bool +usage(FILE *stream) +{ + return + fprintf( + stream, +"Usage: %s [OPTION]...\n" +"Dump USB device HID report descriptor(s) and/or stream(s).\n" +"\n" +"Options:\n" +" -h, --help output this help message and exit\n" +" -v, --version output version information and exit\n" +"\n" +" -s, -a, --address=bus[:dev] limit interfaces by bus number\n" +" (1-255) and device address (1-255),\n" +" decimal; zeroes match any\n" +" -d, -m, --model=vid[:pid] limit interfaces by vendor and\n" +" product IDs (0001-ffff), hexadecimal;\n" +" zeroes match any\n" +" -i, --interface=NUMBER limit interfaces by number (0-254),\n" +" decimal; 255 matches any\n" +"\n" +" -e, --entity=STRING what to dump: either \"descriptor\",\n" +" \"stream\" or \"all\"; value can be\n" +" abbreviated\n" +"\n" +" -p, --stream-paused start with the stream dump output\n" +" paused\n" +" -f, --stream-feedback enable stream dumping feedback: for\n" +" every transfer dumped a dot is\n" +" printed to stderr\n" +"\n" +"Default options: --entity=descriptor\n" +"\n" +"Signals:\n" +" USR1/USR2 pause/resume the stream dump output\n" +"\n", + program_invocation_short_name) >= 0; +} + + typedef enum opt_val { OPT_VAL_HELP = 'h', OPT_VAL_VERSION = 'v', + OPT_VAL_ADDRESS = 'a', + OPT_VAL_ADDRESS_COMP = 's', + OPT_VAL_MODEL = 'm', + OPT_VAL_MODEL_COMP = 'd', + OPT_VAL_INTERFACE = 'i', OPT_VAL_ENTITY = 'e', OPT_VAL_STREAM_PAUSED = 'p', OPT_VAL_STREAM_FEEDBACK = 'f', } opt_val; +static const struct option long_opt_list[] = { + {.val = OPT_VAL_HELP, + .name = "help", + .has_arg = no_argument, + .flag = NULL}, + {.val = OPT_VAL_VERSION, + .name = "version", + .has_arg = no_argument, + .flag = NULL}, + {.val = OPT_VAL_ADDRESS, + .name = "address", + .has_arg = required_argument, + .flag = NULL}, + {.val = OPT_VAL_MODEL, + .name = "model", + .has_arg = required_argument, + .flag = NULL}, + {.val = OPT_VAL_INTERFACE, + .name = "interface", + .has_arg = required_argument, + .flag = NULL}, + {.val = OPT_VAL_ENTITY, + .name = "entity", + .has_arg = required_argument, + .flag = NULL}, + {.val = OPT_VAL_STREAM_PAUSED, + .name = "stream-paused", + .has_arg = no_argument, + .flag = NULL}, + {.val = OPT_VAL_STREAM_FEEDBACK, + .name = "stream-feedback", + .has_arg = no_argument, + .flag = NULL}, + {.val = 0, + .name = NULL, + .has_arg = 0, + .flag = NULL} +}; + + +static const char *short_opt_list = "hvs::a::d::m::i:e:pf"; + + int main(int argc, char **argv) { - static const struct option long_opt_list[] = { - {.val = OPT_VAL_HELP, - .name = "help", - .has_arg = no_argument, - .flag = NULL}, - {.val = OPT_VAL_VERSION, - .name = "version", - .has_arg = no_argument, - .flag = NULL}, - {.val = OPT_VAL_ENTITY, - .name = "entity", - .has_arg = required_argument, - .flag = NULL}, - {.val = OPT_VAL_STREAM_PAUSED, - .name = "stream-paused", - .has_arg = no_argument, - .flag = NULL}, - {.val = OPT_VAL_STREAM_FEEDBACK, - .name = "stream-feedback", - .has_arg = no_argument, - .flag = NULL}, - {.val = 0, - .name = NULL, - .has_arg = 0, - .flag = NULL} - }; - - static const char *short_opt_list = "hve:pf"; - - int result; - - char c; - const char *bus_str; - const char *dev_str; - const char *if_str = ""; - - const char **arg_list[] = {&bus_str, &dev_str, &if_str}; - const size_t req_arg_num = 2; - const size_t max_arg_num = 3; - size_t i; - char *end; - - bool dump_descriptor = true; - bool dump_stream = false; - long bus_num; - long dev_num; - long if_num = -1; + int result; + + char c; + + uint8_t bus_num = BUS_NUM_ANY; + uint8_t dev_addr = DEV_ADDR_ANY; + + uint16_t vid = VID_ANY; + uint16_t pid = PID_ANY; + + uint8_t iface_num = IFACE_NUM_ANY; + + bool dump_descriptor = true; + bool dump_stream = false; struct sigaction sa; #define USAGE_ERROR(_fmt, _args...) \ - do { \ - fprintf(stderr, _fmt "\n", ##_args); \ - usage(stderr, program_invocation_short_name); \ - return 1; \ + do { \ + fprintf(stderr, _fmt "\n", ##_args); \ + usage(stderr); \ + return 1; \ } while (0) /* @@ -643,13 +840,27 @@ main(int argc, char **argv) switch (c) { case OPT_VAL_HELP: - usage(stdout, program_invocation_short_name); + usage(stdout); return 0; break; case OPT_VAL_VERSION: version(stdout); return 0; break; + case OPT_VAL_ADDRESS: + case OPT_VAL_ADDRESS_COMP: + if (!parse_address(optarg, &bus_num, &dev_addr)) + USAGE_ERROR("Invalid device address \"%s\"", optarg); + break; + case OPT_VAL_MODEL: + case OPT_VAL_MODEL_COMP: + if (!parse_model(optarg, &vid, &pid)) + USAGE_ERROR("Invalid model \"%s\"", optarg); + break; + case OPT_VAL_INTERFACE: + if (!parse_iface_num(optarg, &iface_num)) + USAGE_ERROR("Invalid interface number \"%s\"", optarg); + break; case OPT_VAL_ENTITY: if (strncmp(optarg, "descriptor", strlen(optarg)) == 0) { @@ -677,44 +888,17 @@ main(int argc, char **argv) stream_feedback = 1; break; case '?': - usage(stderr, program_invocation_short_name); + usage(stderr); return 1; break; } } /* - * Assign positional parameters + * Verify positional arguments */ - for (i = 0; i < max_arg_num && optind < argc; i++, optind++) - *arg_list[i] = argv[optind]; - - if (i < req_arg_num) - USAGE_ERROR("Not enough arguments"); - - /* - * Parse and verify positional parameters - */ - errno = 0; - bus_num = strtol(bus_str, &end, 0); - if (errno != 0 || !uhd_strisblank(end) || - bus_num <= 0 || bus_num > 255) - USAGE_ERROR("Invalid bus number \"%s\"", bus_str); - - errno = 0; - dev_num = strtol(dev_str, &end, 0); - if (errno != 0 || !uhd_strisblank(end) || - dev_num <= 0 || dev_num > 255) - USAGE_ERROR("Invalid device address \"%s\"", dev_str); - - if (!uhd_strisblank(if_str)) - { - errno = 0; - if_num = strtol(if_str, &end, 0); - if (errno != 0 || !uhd_strisblank(end) || - if_num < 0 || if_num >= 255) - USAGE_ERROR("Invalid interface number \"%s\"", if_str); - } + if (optind < argc) + USAGE_ERROR("Positional arguments are not accepted"); /* * Setup signal handlers @@ -752,7 +936,9 @@ main(int argc, char **argv) /* Make stdout buffered - we will flush it explicitly */ setbuf(stdout, NULL); - result = run(dump_descriptor, dump_stream, bus_num, dev_num, if_num); + /* Run! */ + result = run(dump_descriptor, dump_stream, + bus_num, dev_addr, vid, pid, iface_num); /* * Restore signal handlers -- cgit v1.2.1