diff options
author | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2019-05-07 15:29:04 +0200 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2019-05-07 15:29:04 +0200 |
commit | e629d869c9b2a0cec0f3abec31b1226cf8b55a39 (patch) | |
tree | e79cd47b3eb61f64693551e0482add5f83877181 | |
parent | cb38b13bf5f9ba921b37607f58e05d3e6a08327c (diff) | |
parent | 648a78cecbbfd3e4c9d8686f55603734b9dae8a9 (diff) | |
download | usbutils-usbhid-dump-merge.tar.gz |
Merge upstream usbhid-dump repo into usbutils repousbhid-dump-merge
This "merges" the usbhid-dump upstream repository (found at
git@github.com:DIGImend/usbhid-dump.git) into the usbutils directory.
We are doing this to make the management of the two simpler to handle
overall, and hopefully reduce the size of the usbhid-dump source tree a
bit.
Note, this was done by "rewriting" the history of the usbhid-dump
repository by moving it into the usbhid-dump subdirectory. All of the
commits are still here, just under a new sha1 because of the rewrite.
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
31 files changed, 3090 insertions, 0 deletions
diff --git a/usbhid-dump/.gitignore b/usbhid-dump/.gitignore new file mode 100644 index 0000000..839e279 --- /dev/null +++ b/usbhid-dump/.gitignore @@ -0,0 +1,16 @@ +/config.h.in +/autom4te.cache +/aclocal.m4 +/Makefile.in +/configure +/config.h +/config.log +/config.status +/libtool +/Makefile +/stamp-h1 +/auxdir +/ChangeLog +*.o +*.lo +*.la diff --git a/usbhid-dump/Makefile.am b/usbhid-dump/Makefile.am new file mode 100644 index 0000000..33d1b4f --- /dev/null +++ b/usbhid-dump/Makefile.am @@ -0,0 +1,35 @@ +# Copyright (C) 2009-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 + +SUBDIRS = include lib src doc + +dist_noinst_SCRIPTS = bootstrap + +ACLOCAL_AMFLAGS = -I m4 + +dist-hook: + @set -e; \ + if test -d "$(srcdir)/.git"; then \ + echo Generating ChangeLog; \ + ( cd "$(srcdir)" && auxdir/missing --run git log ) > \ + ChangeLog.tmp; \ + mv -f ChangeLog.tmp "$(distdir)/ChangeLog"; \ + else \ + echo Skipping ChangeLog generation: no .git directory >&2; \ + fi + diff --git a/usbhid-dump/NEWS b/usbhid-dump/NEWS new file mode 100644 index 0000000..b938f3b --- /dev/null +++ b/usbhid-dump/NEWS @@ -0,0 +1,35 @@ +2014-11-11: 1.4 + * Request report descriptor with size specified in the interface descriptor + to fix retrieval with some devices (e.g. Waltop Vega Tablet) which + would produce ERROR_PIPE otherwise. + * Add missing stdint.h includes to fix Gentoo/FreeBSD build issues. + * Add README.md + +2011-02-05: 1.3 + * Add manual page. + * Fix several build issues. + * Fix infinite loop in command-line option parsing on unsigned char + platforms. + +2010-11-20: 1.2 + * Command line parameters are removed, now all HID interfaces + of all USB devices are dumped by default. This may include the + keyboard controlling the terminal, for example, please be careful. + * Added device selection limiting options: -s, -a, --address=bus[:dev] + to limit by bus number and/or device address and -d, -m, + --model=vid[:pid] to limit by vendor and/or product. + * Added interface number limiting option -i, --interface=NUMBER. + * Added stream interrupt transfer timeout option -t, + --stream-timeout=NUMBER, which is set to 1 minute by default to + allow recovery of accidentially captured terminal input device. + This timeout is printed on the start of stream dumping. + +2010-08-24: 1.1 + * Renamed to usbhid-dump + * Fix handling of responses to unsupported HID requests + * Renamed dump entity "both" to "all" + +2010-07-25: 1.0 + * Report descriptor dumping support + * Report stream dumping support + * Report stream dumping feedback and output pausing support diff --git a/usbhid-dump/README.md b/usbhid-dump/README.md new file mode 100644 index 0000000..59ec2f2 --- /dev/null +++ b/usbhid-dump/README.md @@ -0,0 +1,121 @@ +Usbhid-dump +=========== + +Usbhid-dump is a USB HID dumping utility based on libusb 1.0. It dumps USB HID device report descriptors and reports themselves as they are being sent, for all or specific device interfaces. + +Installation +------------ + +Run `./configure && make` to build and `make install` to install. If building from a Git tree, run `./bootstrap` first. Usbhid-dump can also be run directly from the source directory as `src/usbhid-dump`, without installation. + +Usage +----- + +Here is the output of `usbhid-dump --help`: + + $ usbhid-dump --help + Usage: usbhid-dump [OPTION]... + Dump USB device HID report descriptor(s) and/or stream(s). + + Options: + -h, --help output this help message and exit + -v, --version output version information and exit + + -s, -a, --address=bus[:dev] limit interfaces by bus number + (1-255) and device address (1-255), + decimal; zeroes match any + -d, -m, --model=vid[:pid] limit interfaces by vendor and + product IDs (0001-ffff), hexadecimal; + zeroes match any + -i, --interface=NUMBER limit interfaces by number (0-254), + decimal; 255 matches any + + -e, --entity=STRING what to dump: either "descriptor", + "stream" or "all"; value can be + abbreviated + + -t, --stream-timeout=NUMBER stream interrupt transfer timeout, ms; + zero means infinity + -p, --stream-paused start with the stream dump output + paused + -f, --stream-feedback enable stream dumping feedback: for + every transfer dumped a dot is + printed to stderr + + Default options: --stream-timeout=60000 --entity=descriptor + + Signals: + USR1/USR2 pause/resume the stream dump output + + +**Warning:** please be careful running usbhid-dump as a superuser without limiting your device selection with options. Usbhid-dump will try to dump every device possible and If you're using a USB keyboard to control your terminal, it will be detached and you will be unable to terminate usbhid-dump and regain control. + +If that happens just don't touch your keyboard for the duration of the interrupt transfer timeout (1 minute by default) and the dumping will be aborted. + +Here is an example of a report descriptor and stream dump from a mouse: + + $ sudo usbhid-dump --entity=all --address=2:3 + 002:003:000:DESCRIPTOR 1290272184.081322 + 05 01 09 02 A1 01 09 01 A1 00 05 09 19 01 29 03 + 15 00 25 01 75 01 95 03 81 02 75 05 95 01 81 01 + 05 01 09 30 09 31 09 38 15 81 25 7F 75 08 95 03 + 81 06 C0 C0 + + Starting dumping interrupt transfer stream + with 1 minute timeout. + + 002:003:000:STREAM 1290272185.210022 + 00 FF 00 00 + + 002:003:000:STREAM 1290272185.217988 + 00 FE 00 00 + + 002:003:000:STREAM 1290272185.225985 + 00 FC 01 00 + + 002:003:000:STREAM 1290272185.233995 + 00 FE 01 00 + + 002:003:000:STREAM 1290272185.241992 + 00 FF 01 00 + + 002:003:000:STREAM 1290272185.249995 + 00 FE 02 00 + + 002:003:000:STREAM 1290272185.257993 + 00 FF 01 00 + + ^C + +In the output above "002" is the bus number, "003" is the device address and "000" is the interface number. "DESCRIPTOR" indicates descriptor chunk and "STREAM" - stream chunk. The number to the right is the timestamp in seconds since epoch. The hexadecimal numbers below is the chunk dump itself. Usually every stream chunk includes a whole report, but if the report is bigger than endpoint's wMaxPacketSize, it will span several chunks. + +You can use usbhid-dump along with [hidrd-convert](https://github.com/DIGImend/hidrd) to dump report descriptors in human-readable format. Like this: + + $ sudo usbhid-dump -a2:3 -i0 | grep -v : | xxd -r -p | hidrd-convert -o spec + Usage Page (Desktop), ; Generic desktop controls (01h) + Usage (Mouse), ; Mouse (02h, application collection) + Collection (Application), + Usage (Pointer), ; Pointer (01h, physical collection) + Collection (Physical), + Usage Page (Button), ; Button (09h) + Usage Minimum (01h), + Usage Maximum (03h), + Logical Minimum (0), + Logical Maximum (1), + Report Size (1), + Report Count (3), + Input (Variable), + Report Size (5), + Report Count (1), + Input (Constant), + Usage Page (Desktop), ; Generic desktop controls (01h) + Usage (X), ; X (30h, dynamic value) + Usage (Y), ; Y (31h, dynamic value) + Usage (Wheel), ; Wheel (38h, dynamic value) + Logical Minimum (-127), + Logical Maximum (127), + Report Size (8), + Report Count (3), + Input (Variable, Relative), + End Collection, + End Collection diff --git a/usbhid-dump/auxdir/.gitignore b/usbhid-dump/auxdir/.gitignore new file mode 100644 index 0000000..d8263c6 --- /dev/null +++ b/usbhid-dump/auxdir/.gitignore @@ -0,0 +1,6 @@ +/depcomp +/missing +/config.guess +/ltmain.sh +/config.sub +/install-sh diff --git a/usbhid-dump/bootstrap b/usbhid-dump/bootstrap new file mode 100755 index 0000000..4f2509b --- /dev/null +++ b/usbhid-dump/bootstrap @@ -0,0 +1,28 @@ +#!/bin/bash +# +# 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 + +# Fail on any error +set -e + +cat > ChangeLog <<CUT +# Dummy ChangeLog, replaced with git log when distribution is created. +CUT + +autoreconf -i -f diff --git a/usbhid-dump/configure.ac b/usbhid-dump/configure.ac new file mode 100644 index 0000000..3422710 --- /dev/null +++ b/usbhid-dump/configure.ac @@ -0,0 +1,102 @@ +# +# Copyright (C) 2010-2011 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 +# +# Process this file with autoconf to produce a configure script. + +AC_PREREQ(2.61) +AC_INIT([usbhid-dump], [1.4]) +AC_CONFIG_AUX_DIR([auxdir]) +AM_INIT_AUTOMAKE([1.9 -Wall foreign]) +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) +AM_MAINTAINER_MODE +AC_CONFIG_HEADER([config.h]) +AC_CONFIG_MACRO_DIR([m4]) + +# To have empty CFLAGS instead of undefined and '-g -O2' by default +CFLAGS=$CFLAGS +CFLAGS="-Os -Wall $CFLAGS" +ABS_SRCDIR=`cd ${srcdir} ; pwd` +ABS_BUILDDIR=`pwd` +CPPFLAGS="-I${ABS_BUILDDIR} -I${ABS_BUILDDIR}/include -DNDEBUG $CPPFLAGS" +if test "$ABS_SRCDIR" != "$ABS_BUILDDIR"; then + CPPFLAGS="-I${ABS_SRCDIR}/include $CPPFLAGS" +fi + + +# +# Checks for programs. +# +AC_PROG_CC +AC_PROG_INSTALL +m4_ifdef([AM_PROG_AR], [AM_PROG_AR]) +AC_PROG_LIBTOOL + +# +# Checks for libraries. +# +PKG_CHECK_MODULES(LIBUSB, libusb-1.0 >= 1.0.0) +CFLAGS="$CFLAGS $LIBUSB_CFLAGS" +LIBS="$LIBS $LIBUSB_LIBS" + +# +# Checks for features +# +AC_ARG_ENABLE( + debug, + AS_HELP_STRING([--enable-debug], [enable debugging features]), + [], [enable_debug="no"]) + +AC_ARG_ENABLE( + tests-install, + AS_HELP_STRING([--enable-tests-install], [enable installation of tests]), + [], [enable_tests_install="no"]) + +# Output features to preprocessor and compiler +if test "$enable_debug" = "yes"; then + CPPFLAGS="$CPPFLAGS -UNDEBUG" + CFLAGS="$CFLAGS -Wextra -Werror -g -O0" +fi + +# +# Checks for header files. +# + +# +# Checks for typedefs, structures, and compiler characteristics. +# + +# +# Checks for declarations +# + +# +# Checks for library functions. +# +AC_CHECK_FUNCS([libusb_strerror libusb_set_option]) + +# +# Output +# +AC_CONFIG_FILES([Makefile + include/Makefile + include/uhd/Makefile + lib/Makefile + src/Makefile + doc/Makefile]) +AC_OUTPUT diff --git a/usbhid-dump/doc/.gitignore b/usbhid-dump/doc/.gitignore new file mode 100644 index 0000000..b336cc7 --- /dev/null +++ b/usbhid-dump/doc/.gitignore @@ -0,0 +1,2 @@ +/Makefile +/Makefile.in diff --git a/usbhid-dump/doc/Makefile.am b/usbhid-dump/doc/Makefile.am new file mode 100644 index 0000000..4435bdb --- /dev/null +++ b/usbhid-dump/doc/Makefile.am @@ -0,0 +1,19 @@ +# 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 + +dist_man_MANS = usbhid-dump.8 diff --git a/usbhid-dump/doc/usbhid-dump.8 b/usbhid-dump/doc/usbhid-dump.8 new file mode 100644 index 0000000..92753e7 --- /dev/null +++ b/usbhid-dump/doc/usbhid-dump.8 @@ -0,0 +1,118 @@ +.\" Process this file with +.\" groff -man -Tascii usbhid-dump.8 +.\" +.\" 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 +.\" +.TH usbhid-dump "8" "February 2012" +.SH NAME +usbhid-dump \- dump USB HID device report descriptors and streams +.SH SYNOPSIS +.B usbhid-dump +[OPTION]... +.SH DESCRIPTION +.B usbhid-dump +uses +.B libusb +to dump report descriptors and streams from HID (human interface device) +interfaces of USB devices. By default, it dumps HID interfaces of all +connected USB devices, but could be limited to a subset of them, or to a single +interface, using options. + +NOTE: usbhid-dump detaches kernel drivers from the interfaces it dumps and uses +them exclusively, so no other program receives the input in the meantime. The +report descriptor dumping is instantaneous, but the stream dumping continues +until terminated with SIGINT (^C from the terminal) or a timeout expires. + +If you accidentally start dumping a stream from the USB keyboard you use to +control the terminal, the system will stop receiving the input and you won't +be able to terminate usbhid-dump. Just stop your input and wait until the +timeout expires. The stream dumping will stop, the keyboard will be +reattached to the kernel driver and you will regain control. + +The default stream dumping timeout is 60 seconds and could be changed with the +-t option. +.SH OPTIONS +.TP +.B -h, --help +Output a help message and exit. +.TP +.B -v, --version +Output version information and exit. +.TP +.B -s, -a, --address=bus[:dev] +Limit interfaces by bus number and device address. Both 1-255, decimal. +Zeroes match any bus or device. +.TP +.B -d, -m, --model=vid[:pid] +Limit interfaces by device vendor and product IDs. Both 1-FFFF, hexadecimal. +Zeroes match any vendor or product. +.TP +.B -i, --interface=NUMBER +Limit interfaces by number (0-254), decimal. 255 matches any interface. +.TP +.B -e, --entity=STRING +The entity to dump: either "descriptor", "stream" or "all". The value can be +abbreviated down to one letter. The default is "descriptor". +.TP +.B -t, --stream-timeout=NUMBER +Stream interrupt transfer timeout, ms. Zero means infinity. The default is +60000 (60 seconds). +.TP +.B -p, --stream-paused +Start with the stream dump output paused. +.TP +.B -f, --stream-feedback +Enable stream dumping feedback: print a dot to stderr for every transfer +dumped. +.SH SIGNALS +.TP +.B USR1/USR2 +Pause/resume stream dump output. +.SH OUTPUT FORMAT +.B usbhid-dump +outputs dumps in chunks. Each chunk is separated by an empty line and starts +with the following header line: + +BUS:DEVICE:INTERFACE:ENTITY TIMESTAMP + +Here, BUS, DEVICE and INTERFACE are bus, device and interface numbers +respectively. ENTITY is either "DESCRIPTOR" or "STREAM". TIMESTAMP is +timestamp in seconds since epoch. + +After the header the actual dump data follows as hex bytes. A descriptor +chunk includes the whole report descriptor. Every stream chunk includes a +whole report, usually, but if a report is bigger than endpoint's +wMaxPacketSize, it will span several chunks. +.SH EXAMPLES +.TP +Dump report descriptor for a device with address 3 on bus number 2: +.B usbhid-dump -a 2:3 + +.TP +Dump report stream for a device with vendor ID 0x5543 and product ID 0x0005: +.B usbhid-dump -m 5543:0005 -es + +.TP +Dump report descriptor from interface 1 of a device with vendor ID 0x5543: +.B usbhid-dump -m 5543 -i 1 -ed + +.TP +Dump report streams from all HID interfaces of all USB devices (caution: you will lose control over the terminal if you use USB keyboard): +.B usbhid-dump -es + +.SH AUTHOR +Nikolai Kondrashov <spbnick@gmail.com> diff --git a/usbhid-dump/include/.gitignore b/usbhid-dump/include/.gitignore new file mode 100644 index 0000000..9ee6454 --- /dev/null +++ b/usbhid-dump/include/.gitignore @@ -0,0 +1,2 @@ +/Makefile.in +/Makefile diff --git a/usbhid-dump/include/Makefile.am b/usbhid-dump/include/Makefile.am new file mode 100644 index 0000000..9ad123e --- /dev/null +++ b/usbhid-dump/include/Makefile.am @@ -0,0 +1,19 @@ +# 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 + +SUBDIRS = uhd diff --git a/usbhid-dump/include/uhd/.gitignore b/usbhid-dump/include/uhd/.gitignore new file mode 100644 index 0000000..9ee6454 --- /dev/null +++ b/usbhid-dump/include/uhd/.gitignore @@ -0,0 +1,2 @@ +/Makefile.in +/Makefile diff --git a/usbhid-dump/include/uhd/Makefile.am b/usbhid-dump/include/uhd/Makefile.am new file mode 100644 index 0000000..1083140 --- /dev/null +++ b/usbhid-dump/include/uhd/Makefile.am @@ -0,0 +1,25 @@ +# 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 + +noinst_HEADERS = \ + dev.h \ + dev_list.h \ + iface.h \ + iface_list.h \ + libusb.h \ + misc.h diff --git a/usbhid-dump/include/uhd/dev.h b/usbhid-dump/include/uhd/dev.h new file mode 100644 index 0000000..a97462a --- /dev/null +++ b/usbhid-dump/include/uhd/dev.h @@ -0,0 +1,76 @@ +/** @file + * @brief usbhid-dump - device + * + * Copyright (C) 2010-2011 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 <spbnick@gmail.com> + * + * @(#) $Id$ + */ + +#ifndef __UHD_DEV_H__ +#define __UHD_DEV_H__ + +#include <stdbool.h> +#include "uhd/libusb.h" + +#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/usbhid-dump/include/uhd/dev_list.h b/usbhid-dump/include/uhd/dev_list.h new file mode 100644 index 0000000..ecc4739 --- /dev/null +++ b/usbhid-dump/include/uhd/dev_list.h @@ -0,0 +1,110 @@ +/** @file + * @brief usbhid-dump - device list + * + * Copyright (C) 2010-2011 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 <spbnick@gmail.com> + * + * @(#) $Id$ + */ + +#ifndef __UHD_DEV_LIST_H__ +#define __UHD_DEV_LIST_H__ + +#include <stddef.h> +#include <stdint.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 vid Vendor ID, or 0 for any vendor. + * @param pid 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 vid, + uint16_t pid, + uhd_dev **plist); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __UHD_DEV_LIST_H__ */ diff --git a/usbhid-dump/include/uhd/iface.h b/usbhid-dump/include/uhd/iface.h new file mode 100644 index 0000000..9317015 --- /dev/null +++ b/usbhid-dump/include/uhd/iface.h @@ -0,0 +1,177 @@ +/** @file + * @brief usbhid-dump - interface + * + * 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 <spbnick@gmail.com> + * + * @(#) $Id$ + */ + +#ifndef __UHD_IFACE_H__ +#define __UHD_IFACE_H__ + +#include <stdint.h> +#include "uhd/dev.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** usbhid-dump interface */ +typedef struct uhd_iface uhd_iface; + +struct uhd_iface { + uhd_iface *next; + 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 */ + uint16_t rd_len; /**< Report descriptor length */ + bool detached; /**< True if the interface was + detached from the kernel + driver, false otherwise */ + bool claimed; /**< True if the interface was + claimed */ + /* + * This is somewhat hackish and doesn't belong here, since theoretically + * there could be more than one transfer submitted for an interface. + * However, we don't do it yet. This flag is used to track transfer + * cancellation during stream dumping. + */ + bool submitted; /**< True if an asynchronous + transfer has been submitted + for the interface */ +}; + +/** + * Check if an interface is valid. + * + * @param iface Interface. + * + * @return True if the interface is valid, false otherwise. + */ +extern bool uhd_iface_valid(const uhd_iface *iface); + +/** + * Create a new interface. + * + * @param handle Device handle. + * @param number Interface number. + * @param int_in_ep_addr Interrupt in endpoint address. + * @param int_in_ep_maxp Interrupt in endpoint maximum packet size. + * @param rd_len Report descriptor length. + * + * @return New interface or NULL, if failed to allocate. + */ +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, + uint16_t rd_len); + +/** + * Free an interface. + * + * @param iface The interface to free, could be NULL. + */ +extern void uhd_iface_free(uhd_iface *iface); + +/** + * Detach an interface from its kernel driver (if any). + * + * @param iface The interface to detach. + * + * @return Libusb error code. + */ +extern enum libusb_error uhd_iface_detach(uhd_iface *iface); + +/** + * Attach an interface to its kernel driver (if detached before). + * + * @param iface The interface to attach. + * + * @return Libusb error code. + */ +extern enum libusb_error uhd_iface_attach(uhd_iface *iface); + +/** + * Claim an interface. + * + * @param iface The interface to claim. + * + * @return Libusb error code. + */ +extern enum libusb_error uhd_iface_claim(uhd_iface *iface); + +/** + * Set idle duration on an interface; ignore errors indicating missing + * support. + * + * @param iface The interface to set idle duration on. + * @param duration The duration in 4 ms steps starting from 4 ms. + * @param timeout The request timeout, ms. + * + * @return Libusb error code. + */ +extern enum libusb_error uhd_iface_set_idle( + const uhd_iface *iface, + uint8_t duration, + unsigned int timeout); + +/** + * Set HID protocol on an interface; ignore errors indicating missing + * support. + * + * @param iface The interface to set idle duration on. + * @param report True for "report" protocol, false for "boot" protocol. + * @param timeout The request timeout, ms. + * + * @return Libusb error code. + */ +extern enum libusb_error uhd_iface_set_protocol( + const uhd_iface *iface, + bool report, + unsigned int timeout); + +/** + * Clear halt condition on the input interrupt endpoint of an interface. + * + * @param iface The interface to clear halt condition on. + * + * @return Libusb error code. + */ +extern enum libusb_error uhd_iface_clear_halt(uhd_iface *iface); + +/** + * Release an interface (if claimed before). + * + * @param iface The interface to release. + * + * @return Libusb error code. + */ +extern enum libusb_error uhd_iface_release(uhd_iface *iface); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __UHD_IFACE_H__ */ diff --git a/usbhid-dump/include/uhd/iface_list.h b/usbhid-dump/include/uhd/iface_list.h new file mode 100644 index 0000000..472e415 --- /dev/null +++ b/usbhid-dump/include/uhd/iface_list.h @@ -0,0 +1,111 @@ +/** @file + * @brief usbhid-dump - interface 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 <spbnick@gmail.com> + * + * @(#) $Id$ + */ + +#ifndef __UHD_IFACE_LIST_H__ +#define __UHD_IFACE_LIST_H__ + +#include <stdint.h> +#include "uhd/dev_list.h" +#include "uhd/iface.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Check if an interface list is valid. + * + * @param list Interface list to check. + * + * @return True if the interface list is valid, false otherwise. + */ +extern bool uhd_iface_list_valid(const uhd_iface *list); + +/** + * Check if an interface list is empty. + * + * @param list Interface list to check. + * + * @return True if the interface list is empty, false otherwise. + */ +static inline bool +uhd_iface_list_empty(const uhd_iface *list) +{ + return list == NULL; +} + +/** + * Calculate length of an interface list. + * + * @param list The list to calculate length of. + * + * @return The list length. + */ +extern size_t uhd_iface_list_len(const uhd_iface *list); + +/** + * Free an interface list. + * + * @param list The interface list to free. + */ +extern void uhd_iface_list_free(uhd_iface *list); + +/** + * Iterate over an interface list. + * + * @param _iface Loop interface variable. + * @param _list Interface list to iterate over. + */ +#define UHD_IFACE_LIST_FOR_EACH(_iface, _list) \ + for (_iface = _list; _iface != NULL; _iface = _iface->next) + +/** + * Fetch a list of HID interfaces from a device list. + * + * @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. + */ +extern enum libusb_error uhd_iface_list_new(uhd_dev *dev_list, + uhd_iface **plist); + +/** + * Filter an interface list by an interface number. + * + * @param plist The original list head. + * @param number The interface number to match against. + * + * @return The resulting list head. + */ +extern uhd_iface *uhd_iface_list_fltr_by_num(uhd_iface *list, + uint8_t number); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __UHD_IFACE_LIST_H__ */ diff --git a/usbhid-dump/include/uhd/libusb.h b/usbhid-dump/include/uhd/libusb.h new file mode 100644 index 0000000..ab2c9fd --- /dev/null +++ b/usbhid-dump/include/uhd/libusb.h @@ -0,0 +1,44 @@ +/** @file + * @brief usbhid-dump - libusb API extensions + * + * Copyright (C) 2010-2011 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 <spbnick@gmail.com> + * + * @(#) $Id$ + */ + +#ifndef __UHD_LIBUSB_H__ +#define __UHD_LIBUSB_H__ + +#include <libusb.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef HAVE_LIBUSB_STRERROR +extern const char *libusb_strerror(enum libusb_error err); +#endif + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __UHD_LIBUSB_H__ */ diff --git a/usbhid-dump/include/uhd/misc.h b/usbhid-dump/include/uhd/misc.h new file mode 100644 index 0000000..bcb79de --- /dev/null +++ b/usbhid-dump/include/uhd/misc.h @@ -0,0 +1,89 @@ +/** @file + * @brief usbhid-dump - miscellaneous declarations + * + * 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 <spbnick@gmail.com> + * + * @(#) $Id$ + */ + +#ifndef __UHD_MISC_H__ +#define __UHD_MISC_H__ + +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#pragma pack(1) + +/** HID extra descriptor record */ +typedef struct uhd_hid_descriptor_extra uhd_hid_descriptor_extra; + +struct uhd_hid_descriptor_extra { + uint8_t bDescriptorType; + uint16_t wDescriptorLength; +}; + +/** HID class-specific descriptor */ +typedef struct uhd_hid_descriptor uhd_hid_descriptor; + +struct uhd_hid_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bcdHID; + uint8_t bCountryCode; + uint8_t bNumDescriptors; + uhd_hid_descriptor_extra extra[1]; +}; + +#pragma pack() + +/** + * Maximum descriptor size. + * + * @note 4096 here is maximum control buffer length. + */ +#define UHD_MAX_DESCRIPTOR_SIZE 4096 + +/** Generic USB I/O timeout, ms */ +#define UHD_IO_TIMEOUT 1000 + +/** Wildcard bus number */ +#define UHD_BUS_NUM_ANY 0 + +/** Wildcard device address */ +#define UHD_DEV_ADDR_ANY 0 + +/** Wildcard vendor ID */ +#define UHD_VID_ANY 0 + +/** Wildcard product ID */ +#define UHD_PID_ANY 0 + +/** Wildcard interface number */ +#define UHD_IFACE_NUM_ANY UINT8_MAX + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __UHD_MISC_H__ */ diff --git a/usbhid-dump/lib/.gitignore b/usbhid-dump/lib/.gitignore new file mode 100644 index 0000000..5c88e1b --- /dev/null +++ b/usbhid-dump/lib/.gitignore @@ -0,0 +1,4 @@ +/Makefile.in +/.deps +/.libs +/Makefile diff --git a/usbhid-dump/lib/Makefile.am b/usbhid-dump/lib/Makefile.am new file mode 100644 index 0000000..79a18bb --- /dev/null +++ b/usbhid-dump/lib/Makefile.am @@ -0,0 +1,26 @@ +# Copyright (C) 2009-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 + +noinst_LTLIBRARIES = libuhd.la + +libuhd_la_SOURCES = \ + dev.c \ + dev_list.c \ + iface.c \ + iface_list.c \ + libusb.c diff --git a/usbhid-dump/lib/dev.c b/usbhid-dump/lib/dev.c new file mode 100644 index 0000000..d72e4c1 --- /dev/null +++ b/usbhid-dump/lib/dev.c @@ -0,0 +1,91 @@ +/** @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 <spbnick@gmail.com> + * + * @(#) $Id$ + */ + +#include "config.h" + +#include "uhd/dev.h" +#include <assert.h> +#include <stdlib.h> + +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/usbhid-dump/lib/dev_list.c b/usbhid-dump/lib/dev_list.c new file mode 100644 index 0000000..7003ad5 --- /dev/null +++ b/usbhid-dump/lib/dev_list.c @@ -0,0 +1,156 @@ +/** @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 <spbnick@gmail.com> + * + * @(#) $Id$ + */ + +#include "config.h" + +#include "uhd/misc.h" +#include "uhd/dev_list.h" +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> + +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 vid, uint16_t pid, + 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 != UHD_BUS_NUM_ANY && + libusb_get_bus_number(lusb_dev) != bus_num) || + (dev_addr != UHD_DEV_ADDR_ANY && + libusb_get_device_address(lusb_dev) != dev_addr)) + continue; + + /* Skip devices not matching vendor/product mask */ + if (vid != UHD_VID_ANY || pid != UHD_PID_ANY) + { + err = libusb_get_device_descriptor(lusb_dev, &desc); + if (err != LIBUSB_SUCCESS) + goto cleanup; + + if ((vid != UHD_VID_ANY && vid != desc.idVendor) || + (pid != UHD_PID_ANY && pid != 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/usbhid-dump/lib/iface.c b/usbhid-dump/lib/iface.c new file mode 100644 index 0000000..f95182a --- /dev/null +++ b/usbhid-dump/lib/iface.c @@ -0,0 +1,246 @@ +/** @file + * @brief usbhid-dump - interface + * + * 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 <spbnick@gmail.com> + * + * @(#) $Id$ + */ + +#include "config.h" + +#include "uhd/iface.h" +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +bool +uhd_iface_valid(const uhd_iface *iface) +{ + return iface != NULL && + uhd_dev_valid(iface->dev) && + iface->number < UINT8_MAX && + strlen(iface->addr_str) == (sizeof(iface->addr_str) - 1); +} + +uhd_iface * +uhd_iface_new(const uhd_dev *dev, + uint8_t number, + uint8_t int_in_ep_addr, + uint16_t int_in_ep_maxp, + uint16_t rd_len) +{ + uhd_iface *iface; + libusb_device *lusb_dev; + int rc; + + iface = malloc(sizeof(*iface)); + if (iface == NULL) + return NULL; + + iface->next = NULL; + iface->dev = dev; + iface->number = number; + iface->int_in_ep_addr = int_in_ep_addr; + iface->int_in_ep_maxp = int_in_ep_maxp; + iface->rd_len = rd_len; + iface->detached = false; + 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); + (void)rc; + assert(rc == (sizeof(iface->addr_str) - 1)); + + assert(uhd_iface_valid(iface)); + + return iface; +} + + +void +uhd_iface_free(uhd_iface *iface) +{ + if (iface == NULL) + return; + + assert(uhd_iface_valid(iface)); + + free(iface); +} + + +enum libusb_error +uhd_iface_detach(uhd_iface *iface) +{ + enum libusb_error err; + + assert(uhd_iface_valid(iface)); + + err = libusb_detach_kernel_driver(iface->dev->handle, iface->number); + if (err == LIBUSB_SUCCESS) + iface->detached = true; + else if (err != LIBUSB_ERROR_NOT_FOUND) + return err; + + return LIBUSB_SUCCESS; +} + + +enum libusb_error +uhd_iface_attach(uhd_iface *iface) +{ + enum libusb_error err; + + assert(uhd_iface_valid(iface)); + + if (iface->detached) + { + err = libusb_attach_kernel_driver(iface->dev->handle, + iface->number); + if (err != LIBUSB_SUCCESS) + return err; + iface->detached = false; + } + + return LIBUSB_SUCCESS; +} + + +enum libusb_error +uhd_iface_claim(uhd_iface *iface) +{ + enum libusb_error err; + + assert(uhd_iface_valid(iface)); + + err = libusb_claim_interface(iface->dev->handle, iface->number); + if (err != LIBUSB_SUCCESS) + return err; + + iface->claimed = true; + + return LIBUSB_SUCCESS; +} + + +enum libusb_error +uhd_iface_release(uhd_iface *iface) +{ + enum libusb_error err; + + assert(uhd_iface_valid(iface)); + + err = libusb_release_interface(iface->dev->handle, iface->number); + if (err != LIBUSB_SUCCESS) + return err; + + iface->claimed = false; + + return LIBUSB_SUCCESS; +} + + +enum libusb_error +uhd_iface_clear_halt(uhd_iface *iface) +{ + enum libusb_error err; + + assert(uhd_iface_valid(iface)); + + err = libusb_clear_halt(iface->dev->handle, iface->int_in_ep_addr); + if (err != LIBUSB_SUCCESS) + return err; + + return LIBUSB_SUCCESS; +} + + +enum libusb_error +uhd_iface_set_idle(const uhd_iface *iface, + uint8_t duration, + unsigned int timeout) +{ + int rc; + + assert(uhd_iface_valid(iface)); + + rc = libusb_control_transfer(iface->dev->handle, + /* host->device, class, interface */ + 0x21, + /* Set_Idle */ + 0x0A, + /* duration for all report IDs */ + duration << 8, + /* interface */ + iface->number, + NULL, 0, + timeout); + /* + * Ignoring EPIPE, which means a STALL handshake, which is OK on + * control pipes and indicates request is not supported. + * See USB 2.0 spec, 8.4.5 Handshake Packets + */ + if (rc < 0 && rc != LIBUSB_ERROR_PIPE) + return rc; + + return LIBUSB_SUCCESS; +} + + +enum libusb_error +uhd_iface_set_protocol(const uhd_iface *iface, + bool report, + unsigned int timeout) +{ + int rc; + + assert(uhd_iface_valid(iface)); + + rc = libusb_control_transfer(iface->dev->handle, + /* host->device, class, interface */ + 0x21, + /* Set_Protocol */ + 0x0B, + /* 0 - boot, 1 - report */ + report ? 1 : 0, + /* interface */ + iface->number, + NULL, 0, + timeout); + /* + * Ignoring EPIPE, which means a STALL handshake, which is OK on + * control pipes and indicates request is not supported. + * See USB 2.0 spec, 8.4.5 Handshake Packets + */ + if (rc < 0 && rc != LIBUSB_ERROR_PIPE) + return rc; + + return LIBUSB_SUCCESS; +} + + diff --git a/usbhid-dump/lib/iface_list.c b/usbhid-dump/lib/iface_list.c new file mode 100644 index 0000000..bd208aa --- /dev/null +++ b/usbhid-dump/lib/iface_list.c @@ -0,0 +1,239 @@ +/** @file + * @brief usbhid-dump - interface 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 <spbnick@gmail.com> + * + * @(#) $Id$ + */ + +#include "config.h" + +#include "uhd/iface_list.h" +#include "uhd/misc.h" +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> + +bool +uhd_iface_list_valid(const uhd_iface *list) +{ + UHD_IFACE_LIST_FOR_EACH(list, list) + if (!uhd_iface_valid(list)) + return false; + + return true; +} + + +size_t +uhd_iface_list_len(const uhd_iface *list) +{ + size_t len = 0; + + UHD_IFACE_LIST_FOR_EACH(list, list) + len++; + + return len; +} + + +void +uhd_iface_list_free(uhd_iface *list) +{ + uhd_iface *next; + + for (; list != NULL; list = next) + { + next = list->next; + uhd_iface_free(list); + } +} + + +enum libusb_error +uhd_iface_list_new(uhd_dev *dev_list, + uhd_iface **plist) +{ + enum libusb_error err = LIBUSB_ERROR_OTHER; + + uhd_dev *dev; + struct libusb_config_descriptor *config = NULL; + const struct libusb_interface *lusb_iface; + const struct libusb_interface_descriptor *iface_desc; + const uhd_hid_descriptor *hid_desc; + const uhd_hid_descriptor_extra *hid_desc_extra; + uint16_t rd_len; + const struct libusb_endpoint_descriptor *ep_list; + uint8_t ep_num; + const struct libusb_endpoint_descriptor *ep; + uhd_iface *list = NULL; + uhd_iface *iface; + + assert(uhd_dev_list_valid(dev_list)); + + UHD_DEV_LIST_FOR_EACH(dev, dev_list) + { + /* 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++) + { + /* Skip interfaces with altsettings */ + if (lusb_iface->num_altsetting != 1) + continue; + + iface_desc = lusb_iface->altsetting; + + /* Skip non-HID interfaces */ + if (iface_desc->bInterfaceClass != LIBUSB_CLASS_HID) + continue; + + /* + * Try to retrieve report descriptor length + */ + rd_len = UHD_MAX_DESCRIPTOR_SIZE; + /* If interface descriptor has space for a HID descriptor */ + if (iface_desc->extra_length >= (int)sizeof(uhd_hid_descriptor)) + { + hid_desc = (const uhd_hid_descriptor *)iface_desc->extra; + /* If this is truly a HID class descriptor */ + if (hid_desc->bDescriptorType == LIBUSB_DT_HID) + { + /* For each extra HID descriptor entry */ + for (hid_desc_extra = hid_desc->extra; + hid_desc_extra < + hid_desc->extra + hid_desc->bNumDescriptors && + (uint8_t *)hid_desc_extra < + (uint8_t *)hid_desc + hid_desc->bLength && + (unsigned char *)hid_desc_extra < + iface_desc->extra + + iface_desc->extra_length; + hid_desc_extra++) { + /* If this is a report descriptor entry */ + if (hid_desc_extra->bDescriptorType == + LIBUSB_DT_REPORT) + { + rd_len = hid_desc_extra->wDescriptorLength; + break; + } + } + } + } + + /* Retrieve endpoint list */ + ep_list = iface_desc->endpoint; + ep_num = iface_desc->bNumEndpoints; + + /* 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, + iface_desc->bInterfaceNumber, + ep->bEndpointAddress, ep->wMaxPacketSize, + rd_len); + if (iface == NULL) + { + err = LIBUSB_ERROR_NO_MEM; + goto cleanup; + } + + /* Add the interface */ + iface->next = list; + list = iface; + + 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); + + return err; +} + + +uhd_iface * +uhd_iface_list_fltr_by_num(uhd_iface *list, + uint8_t number) +{ + uhd_iface *prev; + uhd_iface *iface; + uhd_iface *next; + + assert(uhd_iface_list_valid(list)); + assert(number < UINT8_MAX); + + for (prev = NULL, iface = list; iface != NULL; iface = next) + { + next = iface->next; + if (iface->number == number) + prev = iface; + else + { + if (prev == NULL) + list = next; + else + prev->next = next; + uhd_iface_free(iface); + } + } + + return list; +} + + diff --git a/usbhid-dump/lib/libusb.c b/usbhid-dump/lib/libusb.c new file mode 100644 index 0000000..74c11cd --- /dev/null +++ b/usbhid-dump/lib/libusb.c @@ -0,0 +1,74 @@ +/** @file + * @brief usbhid-dump - libusb API extensions + * + * 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 <spbnick@gmail.com> + * + * @(#) $Id$ + */ + +#include "config.h" + +#include "uhd/libusb.h" +#include <stdbool.h> + + +#ifndef HAVE_LIBUSB_STRERROR +const char * +libusb_strerror(enum libusb_error err) +{ + switch (err) + { + case LIBUSB_SUCCESS: + return "Success"; +#define MAP(_name, _desc) \ + case LIBUSB_ERROR_##_name: \ + return _desc " (ERROR_" #_name ")" + MAP(IO, + "Input/output error"); + MAP(INVALID_PARAM, + "Invalid parameter"); + MAP(ACCESS, + "Access denied (insufficient permissions)"); + MAP(NO_DEVICE, + "No such device (it may have been disconnected)"); + MAP(NOT_FOUND, + "Entity not found"); + MAP(BUSY, + "Resource busy"); + MAP(TIMEOUT, + "Operation timed out"); + MAP(OVERFLOW, + "Overflow"); + MAP(PIPE, + "Pipe error"); + MAP(INTERRUPTED, + "System call interrupted (perhaps due to signal)"); + MAP(NO_MEM, + "Insufficient memory"); + MAP(NOT_SUPPORTED, + "Operation not supported or unimplemented on this platform"); + MAP(OTHER, "Other error"); +#undef MAP + default: + return "Unknown error code"; + } +} +#endif diff --git a/usbhid-dump/m4/.gitignore b/usbhid-dump/m4/.gitignore new file mode 100644 index 0000000..15c07d6 --- /dev/null +++ b/usbhid-dump/m4/.gitignore @@ -0,0 +1,5 @@ +/ltsugar.m4 +/libtool.m4 +/ltversion.m4 +/lt~obsolete.m4 +/ltoptions.m4 diff --git a/usbhid-dump/src/.gitignore b/usbhid-dump/src/.gitignore new file mode 100644 index 0000000..8223adc --- /dev/null +++ b/usbhid-dump/src/.gitignore @@ -0,0 +1,5 @@ +/Makefile.in +/usbhid-dump +/.deps +/.libs +/Makefile diff --git a/usbhid-dump/src/Makefile.am b/usbhid-dump/src/Makefile.am new file mode 100644 index 0000000..dbe692a --- /dev/null +++ b/usbhid-dump/src/Makefile.am @@ -0,0 +1,22 @@ +# 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 + +bin_PROGRAMS = usbhid-dump + +usbhid_dump_SOURCES = usbhid-dump.c +usbhid_dump_LDADD = ../lib/libuhd.la diff --git a/usbhid-dump/src/usbhid-dump.c b/usbhid-dump/src/usbhid-dump.c new file mode 100644 index 0000000..7f5d962 --- /dev/null +++ b/usbhid-dump/src/usbhid-dump.c @@ -0,0 +1,1085 @@ +/** @file + * @brief usbhid-dump - entry point * + * Copyright (C) 2010-2011 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 <spbnick@gmail.com> + * + * @(#) $Id$ + */ + +#include "config.h" + +#include "uhd/iface_list.h" +#include "uhd/libusb.h" +#include "uhd/misc.h" + +#include <assert.h> +#include <stdbool.h> +#include <ctype.h> +#include <limits.h> +#include <string.h> +#include <errno.h> +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <getopt.h> +#include <stdio.h> +#include <stdint.h> + +/* Define LIBUSB_CALL for libusb <= 1.0.8 */ +#ifndef LIBUSB_CALL +#define LIBUSB_CALL +#endif + +#define GENERIC_ERROR(_fmt, _args...) \ + fprintf(stderr, _fmt "\n", ##_args) + +#define IFACE_ERROR(_iface, _fmt, _args...) \ + GENERIC_ERROR("%s:" _fmt, _iface->addr_str, ##_args) + +#define GENERIC_FAILURE(_fmt, _args...) \ + GENERIC_ERROR("Failed to " _fmt, ##_args) + +#define IFACE_FAILURE(_iface, _fmt, _args...) \ + IFACE_ERROR(_iface, "Failed to " _fmt, ##_args) + +#define LIBUSB_FAILURE(_fmt, _args...) \ + GENERIC_FAILURE(_fmt ": %s", ##_args, libusb_strerror(err)) + +#define LIBUSB_IFACE_FAILURE(_iface, _fmt, _args...) \ + IFACE_FAILURE(_iface, _fmt ": %s", ##_args, libusb_strerror(err)) + +#define ERROR_CLEANUP(_fmt, _args...) \ + do { \ + GENERIC_ERROR(_fmt, ##_args); \ + goto cleanup; \ + } while (0) + +#define FAILURE_CLEANUP(_fmt, _args...) \ + do { \ + GENERIC_FAILURE(_fmt, ##_args); \ + goto cleanup; \ + } while (0) + +#define LIBUSB_FAILURE_CLEANUP(_fmt, _args...) \ + do { \ + LIBUSB_FAILURE(_fmt, ##_args); \ + goto cleanup; \ + } while (0) + +#define LIBUSB_IFACE_FAILURE_CLEANUP(_iface, _fmt, _args...) \ + do { \ + LIBUSB_IFACE_FAILURE(_iface, _fmt, ##_args); \ + goto cleanup; \ + } while (0) + +#define LIBUSB_GUARD(_expr, _fmt, _args...) \ + do { \ + err = _expr; \ + if (err != LIBUSB_SUCCESS) \ + LIBUSB_FAILURE_CLEANUP(_fmt, ##_args); \ + } while (0) + +#define LIBUSB_IFACE_GUARD(_expr, _iface, _fmt, _args...) \ + do { \ + err = _expr; \ + if (err != LIBUSB_SUCCESS) \ + LIBUSB_IFACE_FAILURE_CLEANUP(_iface, _fmt, ##_args); \ + } while (0) + +/**< Number of the signal causing the exit */ +static volatile sig_atomic_t exit_signum = 0; + +static void +exit_sighandler(int signum) +{ + if (exit_signum == 0) + exit_signum = signum; +} + +/**< "Stream paused" flag - non-zero if paused */ +static volatile sig_atomic_t stream_paused = 0; + +static void +stream_pause_sighandler(int signum) +{ + (void)signum; + stream_paused = 1; +} + +static void +stream_resume_sighandler(int signum) +{ + (void)signum; + stream_paused = 0; +} + +/**< "Stream feedback" flag - non-zero if feedback is enabled */ +static volatile sig_atomic_t stream_feedback = 0; + +static void +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"; + size_t pos; + uint8_t b; + struct timeval tv; + + gettimeofday(&tv, NULL); + + fprintf(stdout, "%s:%-16s %12llu.%.6u\n", + iface->addr_str, entity, + (unsigned long long int)tv.tv_sec, + (unsigned int)tv.tv_usec); + + for (pos = 1; len > 0; len--, ptr++, pos++) + { + b = *ptr; + buf[1] = xd[b >> 4]; + buf[2] = xd[b & 0xF]; + + (void)fwrite(buf, ((pos % 16 == 0) ? 4 : 3), 1, stdout); + } + + if (pos % 16 != 1) + fputc('\n', stdout); + fputc('\n', stdout); + + fflush(stdout); +} + + +static bool +dump_iface_list_descriptor(const uhd_iface *list) +{ + const uhd_iface *iface; + uint8_t buf[UHD_MAX_DESCRIPTOR_SIZE]; + int rc; + enum libusb_error err; + + UHD_IFACE_LIST_FOR_EACH(iface, list) + { + if (iface->rd_len > sizeof(buf)) + { + err = LIBUSB_ERROR_NO_MEM; + LIBUSB_IFACE_FAILURE(iface, "report descriptor too long: %hu", + iface->rd_len); + return false; + } + + rc = libusb_control_transfer(iface->dev->handle, + /* See HID spec, 7.1.1 */ + 0x81, + LIBUSB_REQUEST_GET_DESCRIPTOR, + (LIBUSB_DT_REPORT << 8), iface->number, + buf, iface->rd_len, UHD_IO_TIMEOUT); + if (rc < 0) + { + err = rc; + LIBUSB_IFACE_FAILURE(iface, "retrieve report descriptor"); + return false; + } + dump(iface, "DESCRIPTOR", buf, rc); + } + + return true; +} + + +static void LIBUSB_CALL +dump_iface_list_stream_cb(struct libusb_transfer *transfer) +{ + enum libusb_error err; + uhd_iface *iface; + + assert(transfer != NULL); + + iface = (uhd_iface *)transfer->user_data; + assert(uhd_iface_valid(iface)); + + /* Clear interface "has transfer submitted" flag */ + iface->submitted = false; + + switch (transfer->status) + { + case LIBUSB_TRANSFER_COMPLETED: + /* Dump the result */ + if (!stream_paused) + { + dump(iface, "STREAM", + transfer->buffer, transfer->actual_length); + if (stream_feedback) + fputc('.', stderr); + } + /* Resubmit the transfer */ + err = libusb_submit_transfer(transfer); + if (err != LIBUSB_SUCCESS) + LIBUSB_IFACE_FAILURE(iface, "resubmit a transfer"); + else + { + /* Set interface "has transfer submitted" flag */ + iface->submitted = true; + } + break; + +#define MAP(_name, _desc) \ + case LIBUSB_TRANSFER_##_name: \ + IFACE_ERROR(iface, _desc); \ + break + + MAP(ERROR, "Interrupt transfer failed"); + MAP(TIMED_OUT, "Interrupt transfer timed out"); + MAP(STALL, "Interrupt transfer halted (endpoint stalled)"); + MAP(NO_DEVICE, "Device was disconnected"); + MAP(OVERFLOW, "Interrupt transfer overflowed " + "(device sent more data than requested)"); +#undef MAP + + case LIBUSB_TRANSFER_CANCELLED: + break; + } +} + + +static const char * +format_time_interval(unsigned int i) +{ + static char buf[128]; + char *p = buf; + unsigned int h = i / (60 * 60 * 1000); + unsigned int m = (i % (60 * 60 * 1000)) / (60 * 1000); + unsigned int s = (i % (60 * 1000)) / 1000; + unsigned int ms = i % 1000; + +#define FRACTION(_prev_sum, _name, _val) \ + do { \ + if ((_val) > 0) \ + p += snprintf(p, sizeof(buf) - (p - buf), \ + "%s%u " _name "%s", \ + ((_prev_sum) > 0 ? " " : ""), \ + _val, \ + (((_val) == 1) ? "" : "s")); \ + if (p >= (buf + sizeof(buf))) \ + return buf; \ + } while (0) + + FRACTION(0, "hour", h); + FRACTION(h, "minute", m); + FRACTION(h + m, "second", s); + FRACTION(h + m + s, "millisecond", ms); + +#undef FRACTION + + return buf; +} + + +static const char * +format_timeout(unsigned int i) +{ + return (i == 0) ? "infinite" : format_time_interval(i); +} + + +static bool +dump_iface_list_stream(libusb_context *ctx, + uhd_iface *list, + unsigned int timeout) +{ + bool result = false; + enum libusb_error err; + size_t transfer_num = 0; + struct libusb_transfer **transfer_list = NULL; + struct libusb_transfer **ptransfer; + uhd_iface *iface; + bool submitted = false; + + fprintf(stderr, + "Starting dumping interrupt transfer stream\n" + "with %s timeout.\n\n", + format_timeout(timeout)); + + UHD_IFACE_LIST_FOR_EACH(iface, list) + { + /* Set report protocol */ + LIBUSB_IFACE_GUARD(uhd_iface_set_protocol(iface, true, + UHD_IO_TIMEOUT), + iface, "set report protocol"); + /* Set infinite idle duration */ + LIBUSB_IFACE_GUARD(uhd_iface_set_idle(iface, 0, UHD_IO_TIMEOUT), + iface, "set infinite idle duration"); + } + + /* Calculate number of interfaces and thus transfers */ + transfer_num = uhd_iface_list_len(list); + + /* Allocate transfer list */ + transfer_list = malloc(sizeof(*transfer_list) * transfer_num); + if (transfer_list == NULL) + FAILURE_CLEANUP("allocate transfer list"); + + /* Zero transfer list */ + for (ptransfer = transfer_list; + (size_t)(ptransfer - transfer_list) < transfer_num; + ptransfer++) + *ptransfer = NULL; + + /* Allocate transfers */ + for (ptransfer = transfer_list; + (size_t)(ptransfer - transfer_list) < transfer_num; + ptransfer++) + { + *ptransfer = libusb_alloc_transfer(0); + if (*ptransfer == NULL) + FAILURE_CLEANUP("allocate a transfer"); + /* + * Set user_data to NULL explicitly, since libusb_alloc_transfer + * does memset to zero only and zero is not NULL, strictly speaking. + */ + (*ptransfer)->user_data = NULL; + } + + /* Initialize the transfers as interrupt transfers */ + for (ptransfer = transfer_list, iface = list; + (size_t)(ptransfer - transfer_list) < transfer_num; + ptransfer++, iface = iface->next) + { + void *buf; + const size_t len = iface->int_in_ep_maxp; + + /* Allocate the transfer buffer */ + buf = malloc(len); + if (len > 0 && buf == NULL) + FAILURE_CLEANUP("allocate a transfer buffer"); + + /* Initialize the transfer */ + libusb_fill_interrupt_transfer(*ptransfer, + iface->dev->handle, iface->int_in_ep_addr, + buf, len, + dump_iface_list_stream_cb, + (void *)iface, + timeout); + + /* Ask to free the buffer when the transfer is freed */ + (*ptransfer)->flags |= LIBUSB_TRANSFER_FREE_BUFFER; + } + + /* Submit first transfer requests */ + for (ptransfer = transfer_list; + (size_t)(ptransfer - transfer_list) < transfer_num; + ptransfer++) + { + LIBUSB_GUARD(libusb_submit_transfer(*ptransfer), + "submit a transfer"); + /* Set interface "has transfer submitted" flag */ + ((uhd_iface *)(*ptransfer)->user_data)->submitted = true; + /* Set "have any submitted transfers" flag */ + submitted = true; + } + + /* Run the event machine */ + while (submitted && exit_signum == 0) + { + /* Handle the transfer events */ + err = libusb_handle_events(ctx); + if (err != LIBUSB_SUCCESS && err != LIBUSB_ERROR_INTERRUPTED) + LIBUSB_FAILURE_CLEANUP("handle transfer events"); + + /* Check if there are any submitted transfers left */ + submitted = false; + for (ptransfer = transfer_list; + (size_t)(ptransfer - transfer_list) < transfer_num; + ptransfer++) + { + iface = (uhd_iface *)(*ptransfer)->user_data; + + if (iface != NULL && iface->submitted) + submitted = true; + } + } + + /* If all the transfers were terminated unexpectedly */ + if (transfer_num > 0 && !submitted) + ERROR_CLEANUP("No more interfaces to dump"); + + result = true; + +cleanup: + + /* Cancel the transfers */ + if (submitted) + { + submitted = false; + for (ptransfer = transfer_list; + (size_t)(ptransfer - transfer_list) < transfer_num; + ptransfer++) + { + iface = (uhd_iface *)(*ptransfer)->user_data; + + if (iface != NULL && iface->submitted) + { + err = libusb_cancel_transfer(*ptransfer); + if (err == LIBUSB_SUCCESS) + submitted = true; + else + { + LIBUSB_FAILURE("cancel a transfer, ignoring"); + /* + * XXX are we really sure + * the transfer won't be finished? + */ + iface->submitted = false; + } + } + } + } + + /* Wait for transfer cancellation */ + while (submitted) + { + /* Handle cancellation events */ + err = libusb_handle_events(ctx); + if (err != LIBUSB_SUCCESS && err != LIBUSB_ERROR_INTERRUPTED) + { + LIBUSB_FAILURE("handle transfer cancellation events, " + "aborting transfer cancellation"); + break; + } + + /* Check if there are any submitted transfers left */ + submitted = false; + for (ptransfer = transfer_list; + (size_t)(ptransfer - transfer_list) < transfer_num; + ptransfer++) + { + iface = (uhd_iface *)(*ptransfer)->user_data; + + if (iface != NULL && iface->submitted) + submitted = true; + } + } + + /* + * Free transfer list along with non-submitted transfers and their + * buffers. + */ + if (transfer_list != NULL) + { + for (ptransfer = transfer_list; + (size_t)(ptransfer - transfer_list) < transfer_num; + ptransfer++) + { + iface = (uhd_iface *)(*ptransfer)->user_data; + + /* + * Only free a transfer if it is not submitted. Better leak some + * memory than have some important memory overwritten. + */ + if (iface == NULL || !iface->submitted) + libusb_free_transfer(*ptransfer); + } + + free(transfer_list); + } + + return result; +} + + +static int +run(bool dump_descriptor, + bool dump_stream, + unsigned int stream_timeout, + 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; + 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 to informational only */ +#if HAVE_LIBUSB_SET_OPTION + libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO); +#else + libusb_set_debug(ctx, LIBUSB_LOG_LEVEL_INFO); +#endif + + /* 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 the device list */ + LIBUSB_GUARD(uhd_iface_list_new(dev_list, &iface_list), + "find HID interfaces"); + + /* Filter the interface list by specified interface number */ + if (iface_num != UHD_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"); + + /* Detach and claim the interfaces */ + UHD_IFACE_LIST_FOR_EACH(iface, iface_list) + { + LIBUSB_IFACE_GUARD(uhd_iface_detach(iface), + iface, "detach from the kernel driver"); + LIBUSB_IFACE_GUARD(uhd_iface_claim(iface), + iface, "claim"); + } + + /* Run with the prepared interface list */ + result = (!dump_descriptor || dump_iface_list_descriptor(iface_list)) && + (!dump_stream || dump_iface_list_stream(ctx, iface_list, + stream_timeout)) + ? 0 + : 1; + +cleanup: + + /* Release and attach the interfaces back */ + UHD_IFACE_LIST_FOR_EACH(iface, iface_list) + { + err = uhd_iface_release(iface); + if (err != LIBUSB_SUCCESS) + LIBUSB_IFACE_FAILURE(iface, "release"); + + err = uhd_iface_attach(iface); + if (err != LIBUSB_SUCCESS) + LIBUSB_IFACE_FAILURE(iface, "attach to the kernel driver"); + } + + /* Free the interface list */ + uhd_iface_list_free(iface_list); + + /* Close the device list */ + uhd_dev_list_close(dev_list); + + /* Destroy the libusb context */ + if (ctx != NULL) + libusb_exit(ctx); + + return result; +} + + +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((int)*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((int)*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((int)*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((int)*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((int)*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 +parse_timeout(const char *str, + unsigned int *ptimeout) +{ + long long timeout; + const char *p; + char *end; + + assert(str != NULL); + + p = str; + + /* Skip space (prevent strtoll doing so) */ + while (isspace((int)*p)) + p++; + + /* Extract timeout */ + errno = 0; + timeout = strtoll(p, &end, 10); + if (errno != 0 || end == p || timeout < 0 || timeout > UINT_MAX) + return false; + + /* Output timeout */ + if (ptimeout != NULL) + *ptimeout = timeout; + + 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 <http://gnu.org/licenses/gpl.html>.\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, const char *name) +{ + 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" +" -t, --stream-timeout=NUMBER stream interrupt transfer timeout, ms;\n" +" zero means infinity\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: --stream-timeout=60000 --entity=descriptor\n" +"\n" +"Signals:\n" +" USR1/USR2 pause/resume the stream dump output\n" +"\n", + 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_TIMEOUT = 't', + 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_TIMEOUT, + .name = "stream-timeout", + .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:t:pf"; + + +int +main(int argc, char **argv) +{ + int result; + + const char *name; + + int c; + + uint8_t bus_num = UHD_BUS_NUM_ANY; + uint8_t dev_addr = UHD_DEV_ADDR_ANY; + + uint16_t vid = UHD_VID_ANY; + uint16_t pid = UHD_PID_ANY; + + uint8_t iface_num = UHD_IFACE_NUM_ANY; + + bool dump_descriptor = true; + bool dump_stream = false; + unsigned int stream_timeout = 60000; + + struct sigaction sa; + + /* + * Extract program invocation name + */ + name = rindex(argv[0], '/'); + if (name == NULL) + name = argv[0]; + else + name++; + +#define USAGE_ERROR(_fmt, _args...) \ + do { \ + fprintf(stderr, _fmt "\n", ##_args); \ + usage(stderr, name); \ + return 1; \ + } while (0) + + /* + * Parse command line arguments + */ + while ((c = getopt_long(argc, argv, + short_opt_list, long_opt_list, NULL)) >= 0) + { + switch (c) + { + case OPT_VAL_HELP: + usage(stdout, name); + 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) + { + dump_descriptor = true; + dump_stream = false; + } + else if (strncmp(optarg, "stream", strlen(optarg)) == 0) + { + dump_descriptor = false; + dump_stream = true; + } + else if (strncmp(optarg, "all", strlen(optarg)) == 0) + { + dump_descriptor = true; + dump_stream = true; + } + else + USAGE_ERROR("Unknown entity \"%s\"", optarg); + + break; + case OPT_VAL_STREAM_TIMEOUT: + if (!parse_timeout(optarg, &stream_timeout)) + USAGE_ERROR("Invalid stream timeout \"%s\"", optarg); + break; + case OPT_VAL_STREAM_PAUSED: + stream_paused = 1; + break; + case OPT_VAL_STREAM_FEEDBACK: + stream_feedback = 1; + break; + case '?': + usage(stderr, name); + return 1; + break; + } + } + + /* + * Verify positional arguments + */ + if (optind < argc) + USAGE_ERROR("Positional arguments are not accepted"); + + /* + * Setup signal handlers + */ + /* Setup SIGINT to terminate gracefully */ + sigaction(SIGINT, NULL, &sa); + if (sa.sa_handler != SIG_IGN) + { + sa.sa_handler = exit_sighandler; + sigemptyset(&sa.sa_mask); + sigaddset(&sa.sa_mask, SIGTERM); + sa.sa_flags = 0; /* NOTE: no SA_RESTART on purpose */ + sigaction(SIGINT, &sa, NULL); + } + + /* Setup SIGTERM to terminate gracefully */ + sigaction(SIGTERM, NULL, &sa); + if (sa.sa_handler != SIG_IGN) + { + sa.sa_handler = exit_sighandler; + sigemptyset(&sa.sa_mask); + sigaddset(&sa.sa_mask, SIGINT); + sa.sa_flags = 0; /* NOTE: no SA_RESTART on purpose */ + sigaction(SIGTERM, &sa, NULL); + } + + /* Setup SIGUSR1/SIGUSR2 to pause/resume the stream output */ + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sa.sa_handler = stream_pause_sighandler; + sigaction(SIGUSR1, &sa, NULL); + sa.sa_handler = stream_resume_sighandler; + sigaction(SIGUSR2, &sa, NULL); + + /* Make stdout buffered - we will flush it explicitly */ + setbuf(stdout, NULL); + + /* Run! */ + result = run(dump_descriptor, dump_stream, stream_timeout, + bus_num, dev_addr, vid, pid, iface_num); + + /* + * Restore signal handlers + */ + sigaction(SIGINT, NULL, &sa); + if (sa.sa_handler != SIG_IGN) + signal(SIGINT, SIG_DFL); + + sigaction(SIGTERM, NULL, &sa); + if (sa.sa_handler != SIG_IGN) + signal(SIGTERM, SIG_DFL); + + /* + * Reproduce the signal used to stop the program to get proper exit + * status. + */ + if (exit_signum != 0) + raise(exit_signum); + + return result; +} + + |