summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>2019-05-07 15:29:04 +0200
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2019-05-07 15:29:04 +0200
commite629d869c9b2a0cec0f3abec31b1226cf8b55a39 (patch)
treee79cd47b3eb61f64693551e0482add5f83877181
parentcb38b13bf5f9ba921b37607f58e05d3e6a08327c (diff)
parent648a78cecbbfd3e4c9d8686f55603734b9dae8a9 (diff)
downloadusbutils-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>
-rw-r--r--usbhid-dump/.gitignore16
-rw-r--r--usbhid-dump/Makefile.am35
-rw-r--r--usbhid-dump/NEWS35
-rw-r--r--usbhid-dump/README.md121
-rw-r--r--usbhid-dump/auxdir/.gitignore6
-rwxr-xr-xusbhid-dump/bootstrap28
-rw-r--r--usbhid-dump/configure.ac102
-rw-r--r--usbhid-dump/doc/.gitignore2
-rw-r--r--usbhid-dump/doc/Makefile.am19
-rw-r--r--usbhid-dump/doc/usbhid-dump.8118
-rw-r--r--usbhid-dump/include/.gitignore2
-rw-r--r--usbhid-dump/include/Makefile.am19
-rw-r--r--usbhid-dump/include/uhd/.gitignore2
-rw-r--r--usbhid-dump/include/uhd/Makefile.am25
-rw-r--r--usbhid-dump/include/uhd/dev.h76
-rw-r--r--usbhid-dump/include/uhd/dev_list.h110
-rw-r--r--usbhid-dump/include/uhd/iface.h177
-rw-r--r--usbhid-dump/include/uhd/iface_list.h111
-rw-r--r--usbhid-dump/include/uhd/libusb.h44
-rw-r--r--usbhid-dump/include/uhd/misc.h89
-rw-r--r--usbhid-dump/lib/.gitignore4
-rw-r--r--usbhid-dump/lib/Makefile.am26
-rw-r--r--usbhid-dump/lib/dev.c91
-rw-r--r--usbhid-dump/lib/dev_list.c156
-rw-r--r--usbhid-dump/lib/iface.c246
-rw-r--r--usbhid-dump/lib/iface_list.c239
-rw-r--r--usbhid-dump/lib/libusb.c74
-rw-r--r--usbhid-dump/m4/.gitignore5
-rw-r--r--usbhid-dump/src/.gitignore5
-rw-r--r--usbhid-dump/src/Makefile.am22
-rw-r--r--usbhid-dump/src/usbhid-dump.c1085
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;
+}
+
+