diff options
Diffstat (limited to 'usbhid-dump/src')
-rw-r--r-- | usbhid-dump/src/Makefile.am | 19 | ||||
-rw-r--r-- | usbhid-dump/src/Makefile.in | 609 | ||||
-rw-r--r-- | usbhid-dump/src/dev.c | 72 | ||||
-rw-r--r-- | usbhid-dump/src/dev.h | 57 | ||||
-rw-r--r-- | usbhid-dump/src/dev_list.c | 125 | ||||
-rw-r--r-- | usbhid-dump/src/dev_list.h | 69 | ||||
-rw-r--r-- | usbhid-dump/src/iface.c | 227 | ||||
-rw-r--r-- | usbhid-dump/src/iface.h | 158 | ||||
-rw-r--r-- | usbhid-dump/src/iface_list.c | 220 | ||||
-rw-r--r-- | usbhid-dump/src/iface_list.h | 92 | ||||
-rw-r--r-- | usbhid-dump/src/misc.h | 70 | ||||
-rw-r--r-- | usbhid-dump/src/usbhid-dump.c | 1067 |
12 files changed, 2785 insertions, 0 deletions
diff --git a/usbhid-dump/src/Makefile.am b/usbhid-dump/src/Makefile.am new file mode 100644 index 0000000..262bb74 --- /dev/null +++ b/usbhid-dump/src/Makefile.am @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2010 Nikolai Kondrashov + +noinst_HEADERS = \ + dev.h \ + dev_list.h \ + iface.h \ + iface_list.h \ + misc.h + +bin_PROGRAMS = usbhid-dump + +usbhid_dump_SOURCES = \ + dev.c \ + dev_list.c \ + iface.c \ + iface_list.c \ + usbhid-dump.c diff --git a/usbhid-dump/src/Makefile.in b/usbhid-dump/src/Makefile.in new file mode 100644 index 0000000..5fadc80 --- /dev/null +++ b/usbhid-dump/src/Makefile.in @@ -0,0 +1,609 @@ +# Makefile.in generated by automake 1.16.5 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2021 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright (C) 2010 Nikolai Kondrashov + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +bin_PROGRAMS = usbhid-dump$(EXEEXT) +subdir = src +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" +PROGRAMS = $(bin_PROGRAMS) +am_usbhid_dump_OBJECTS = dev.$(OBJEXT) dev_list.$(OBJEXT) \ + iface.$(OBJEXT) iface_list.$(OBJEXT) usbhid-dump.$(OBJEXT) +usbhid_dump_OBJECTS = $(am_usbhid_dump_OBJECTS) +usbhid_dump_LDADD = $(LDADD) +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/dev.Po ./$(DEPDIR)/dev_list.Po \ + ./$(DEPDIR)/iface.Po ./$(DEPDIR)/iface_list.Po \ + ./$(DEPDIR)/usbhid-dump.Po +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(usbhid_dump_SOURCES) +DIST_SOURCES = $(usbhid_dump_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPPFLAGS = @CPPFLAGS@ +CSCOPE = @CSCOPE@ +CTAGS = @CTAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +ETAGS = @ETAGS@ +EXEEXT = @EXEEXT@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBUSB_CFLAGS = @LIBUSB_CFLAGS@ +LIBUSB_LIBS = @LIBUSB_LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +noinst_HEADERS = \ + dev.h \ + dev_list.h \ + iface.h \ + iface_list.h \ + misc.h + +usbhid_dump_SOURCES = \ + dev.c \ + dev_list.c \ + iface.c \ + iface_list.c \ + usbhid-dump.c + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) + +usbhid-dump$(EXEEXT): $(usbhid_dump_OBJECTS) $(usbhid_dump_DEPENDENCIES) $(EXTRA_usbhid_dump_DEPENDENCIES) + @rm -f usbhid-dump$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(usbhid_dump_OBJECTS) $(usbhid_dump_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dev.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dev_list.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/iface.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/iface_list.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/usbhid-dump.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/dev.Po + -rm -f ./$(DEPDIR)/dev_list.Po + -rm -f ./$(DEPDIR)/iface.Po + -rm -f ./$(DEPDIR)/iface_list.Po + -rm -f ./$(DEPDIR)/usbhid-dump.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/dev.Po + -rm -f ./$(DEPDIR)/dev_list.Po + -rm -f ./$(DEPDIR)/iface.Po + -rm -f ./$(DEPDIR)/iface_list.Po + -rm -f ./$(DEPDIR)/usbhid-dump.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-binPROGRAMS clean-generic cscopelist-am ctags ctags-am \ + distclean distclean-compile distclean-generic distclean-tags \ + distdir dvi dvi-am html html-am info info-am install \ + install-am install-binPROGRAMS install-data install-data-am \ + install-dvi install-dvi-am install-exec install-exec-am \ + install-html install-html-am install-info install-info-am \ + install-man install-pdf install-pdf-am install-ps \ + install-ps-am install-strip installcheck installcheck-am \ + installdirs maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-compile mostlyclean-generic pdf pdf-am \ + ps ps-am tags tags-am uninstall uninstall-am \ + uninstall-binPROGRAMS + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/usbhid-dump/src/dev.c b/usbhid-dump/src/dev.c new file mode 100644 index 0000000..1978496 --- /dev/null +++ b/usbhid-dump/src/dev.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * usbhid-dump - device + * + * Copyright (C) 2010 Nikolai Kondrashov <spbnick@gmail.com> + */ + +#include "config.h" + +#include "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/src/dev.h b/usbhid-dump/src/dev.h new file mode 100644 index 0000000..3f647a5 --- /dev/null +++ b/usbhid-dump/src/dev.h @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * usbhid-dump - device + * + * Copyright (C) 2010-2011 Nikolai Kondrashov <spbnick@gmail.com> + */ + +#ifndef __UHD_DEV_H__ +#define __UHD_DEV_H__ + +#include <stdbool.h> +#include <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/src/dev_list.c b/usbhid-dump/src/dev_list.c new file mode 100644 index 0000000..9848902 --- /dev/null +++ b/usbhid-dump/src/dev_list.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * usbhid-dump - device list + * + * Copyright (C) 2010 Nikolai Kondrashov <spbnick@gmail.com> + */ + +#include "config.h" + +#include "misc.h" +#include "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; +} + + +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/src/dev_list.h b/usbhid-dump/src/dev_list.h new file mode 100644 index 0000000..5c70ad1 --- /dev/null +++ b/usbhid-dump/src/dev_list.h @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * usbhid-dump - device list + * + * Copyright (C) 2010-2011 Nikolai Kondrashov <spbnick@gmail.com> + */ + +#ifndef __UHD_DEV_LIST_H__ +#define __UHD_DEV_LIST_H__ + +#include <stddef.h> +#include <stdint.h> +#include "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); + +/** + * 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/src/iface.c b/usbhid-dump/src/iface.c new file mode 100644 index 0000000..7bcf28b --- /dev/null +++ b/usbhid-dump/src/iface.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * usbhid-dump - interface + * + * Copyright (C) 2010 Nikolai Kondrashov <spbnick@gmail.com> + */ + +#include "config.h" + +#include "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/src/iface.h b/usbhid-dump/src/iface.h new file mode 100644 index 0000000..0e1da6b --- /dev/null +++ b/usbhid-dump/src/iface.h @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * usbhid-dump - interface + * + * Copyright (C) 2010 Nikolai Kondrashov <spbnick@gmail.com> + */ + +#ifndef __UHD_IFACE_H__ +#define __UHD_IFACE_H__ + +#include <stdint.h> +#include "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/src/iface_list.c b/usbhid-dump/src/iface_list.c new file mode 100644 index 0000000..3c82504 --- /dev/null +++ b/usbhid-dump/src/iface_list.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * usbhid-dump - interface list + * + * Copyright (C) 2010 Nikolai Kondrashov <spbnick@gmail.com> + */ + +#include "config.h" + +#include "iface_list.h" +#include "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/src/iface_list.h b/usbhid-dump/src/iface_list.h new file mode 100644 index 0000000..d99f761 --- /dev/null +++ b/usbhid-dump/src/iface_list.h @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * usbhid-dump - interface list + * + * Copyright (C) 2010 Nikolai Kondrashov <spbnick@gmail.com> + */ + +#ifndef __UHD_IFACE_LIST_H__ +#define __UHD_IFACE_LIST_H__ + +#include <stdint.h> +#include "dev_list.h" +#include "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/src/misc.h b/usbhid-dump/src/misc.h new file mode 100644 index 0000000..17b7e6a --- /dev/null +++ b/usbhid-dump/src/misc.h @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * usbhid-dump - miscellaneous declarations + * + * Copyright (C) 2010 Nikolai Kondrashov <spbnick@gmail.com> + */ + +#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/src/usbhid-dump.c b/usbhid-dump/src/usbhid-dump.c new file mode 100644 index 0000000..bd669e2 --- /dev/null +++ b/usbhid-dump/src/usbhid-dump.c @@ -0,0 +1,1067 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * usbhid-dump - entry point * + * + * Copyright (C) 2010-2011 Nikolai Kondrashov <spbnick@gmail.com> + */ + +#include "config.h" + +#include "iface_list.h" +#include "misc.h" +#include <libusb.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 = strrchr(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; +} + + |