diff --git a/libusb/.codespellrc b/libusb/.codespellrc new file mode 100644 index 0000000..dba3ab7 --- /dev/null +++ b/libusb/.codespellrc @@ -0,0 +1,3 @@ +[codespell] +skip = strerror.c,AUTHORS +ignore-words-list = numer,ser,xwindows diff --git a/libusb/.github/workflows/linux.yml b/libusb/.github/workflows/linux.yml index 143db05..cae0184 100644 --- a/libusb/.github/workflows/linux.yml +++ b/libusb/.github/workflows/linux.yml @@ -18,26 +18,33 @@ jobs: - uses: actions/checkout@v3 - name: setup prerequisites - shell: bash run: | sudo apt update sudo apt install autoconf automake libtool libudev-dev m4 - name: bootstrap - shell: bash run: ./bootstrap.sh - name: netlink - shell: bash - run: .private/ci-build.sh --build-dir build-netlink -- --disable-udev + # Disable tests for netlink as it doesn't seem to work in the CI environment. + run: .private/ci-build.sh --build-dir build-netlink --no-test -- --disable-udev - name: udev - shell: bash run: .private/ci-build.sh --build-dir build-udev -- --enable-udev - name: debug-log - shell: bash run: .private/ci-build.sh --build-dir build-debug -- --enable-debug-log + - name: disable-log + run: .private/ci-build.sh --build-dir build-nolog -- --disable-log + + - uses: mymindstorm/setup-emsdk@v13 + + - run: npm ci + working-directory: tests/webusb-test-shim + + - name: emscripten + run: emconfigure .private/ci-build.sh --build-dir build-emscripten -- --host=wasm32-unknown-emscripten + - name: umockdev test run: .private/ci-container-build.sh docker.io/amd64/ubuntu:rolling diff --git a/libusb/.github/workflows/msys2.yml b/libusb/.github/workflows/msys2.yml index b95dd46..dc7f114 100644 --- a/libusb/.github/workflows/msys2.yml +++ b/libusb/.github/workflows/msys2.yml @@ -14,8 +14,10 @@ jobs: msystem: MINGW64 update: true install: git mingw-w64-x86_64-cc mingw-w64-x86_64-autotools - - name: CI-Build - run: | - echo 'Running in MSYS2!' - ./bootstrap.sh - ./.private/ci-build.sh --build-dir build-msys2 + - name: bootstrap + run: ./bootstrap.sh + - name: Build + # GCC on MSYS2 doesn't have ASAN support (but Clang does). + run: ./.private/ci-build.sh --build-dir build-msys2 --no-asan + - name: Build with logging disabled + run: ./.private/ci-build.sh --build-dir build-msys2-nolog --no-asan -- --disable-log diff --git a/libusb/.github/workflows/msys2_clang32.yml b/libusb/.github/workflows/msys2_clang32.yml index de19aa9..3ca91bd 100644 --- a/libusb/.github/workflows/msys2_clang32.yml +++ b/libusb/.github/workflows/msys2_clang32.yml @@ -18,4 +18,8 @@ jobs: run: | echo 'Running in MSYS2!' ./bootstrap.sh - ./.private/ci-build.sh --build-dir build-msys2-clang32 + # Disabling tests as there is some issue that prevents libtool from + # finalizing its executable wrappers. + # Perhaps this one https://github.com/msys2/MSYS2-packages/issues/1351 + # but it only occurs on clang32 configuration. + ./.private/ci-build.sh --build-dir build-msys2-clang32 --no-test diff --git a/libusb/.gitignore b/libusb/.gitignore index d11f8b6..8886c6b 100644 --- a/libusb/.gitignore +++ b/libusb/.gitignore @@ -7,6 +7,7 @@ Makefile.in *.lo *.o *.js +!/tests/webusb-test-shim/*.js *.wasm *.html libtool @@ -21,6 +22,7 @@ depcomp configure aclocal.m4 compile +test-driver config.guess config.h* !msvc/config.h @@ -40,7 +42,13 @@ examples/fxload examples/hotplugtest examples/sam3u_benchmark examples/testlibusb +tests/init_context +tests/macos +tests/set_option tests/stress +tests/stress_mt +tests/*.log +tests/*.trs android/libs android/obj *.exe diff --git a/libusb/.private/appveyor_build.sh b/libusb/.private/appveyor_build.sh index 44ca46b..0f8c34c 100755 --- a/libusb/.private/appveyor_build.sh +++ b/libusb/.private/appveyor_build.sh @@ -19,4 +19,8 @@ echo "Bootstrapping ..." ./bootstrap.sh echo "" -exec .private/ci-build.sh --build-dir "${builddir}" --install -- "--prefix=${installdir}" +extra_args="" +if [ "${Configuration}" == "Release" ]; then + extra_args="--no-asan" +fi +exec .private/ci-build.sh --build-dir "${builddir}" --install ${extra_args} -- "--prefix=${installdir}" diff --git a/libusb/.private/ci-build.sh b/libusb/.private/ci-build.sh index 8a0da6b..33dfb3b 100755 --- a/libusb/.private/ci-build.sh +++ b/libusb/.private/ci-build.sh @@ -3,7 +3,10 @@ set -e builddir= +scriptdir=$(dirname $(readlink -f "$0")) install=no +test=yes +asan=yes while [ $# -gt 0 ]; do case "$1" in @@ -19,6 +22,14 @@ while [ $# -gt 0 ]; do install=yes shift ;; + --no-test) + test=no + shift + ;; + --no-asan) + asan=no + shift + ;; --) shift break; @@ -52,14 +63,28 @@ cflags+=" -Wpointer-arith" cflags+=" -Wredundant-decls" cflags+=" -Wswitch-enum" +# enable address sanitizer +if [ "${asan}" = "yes" ]; then + cflags+=" -fsanitize=address" +fi + echo "" echo "Configuring ..." -CFLAGS="${cflags}" ../configure --enable-examples-build --enable-tests-build "$@" +CFLAGS="${cflags}" CXXFLAGS="${cflags}" ../configure --enable-examples-build --enable-tests-build "$@" echo "" echo "Building ..." make -j4 -k +if [ "${test}" = "yes" ]; then + # Load custom shim for WebUSB tests that simulates Web environment. + export NODE_OPTIONS="--require ${scriptdir}/../tests/webusb-test-shim/" + if ! make check ; then + cat tests/test-suite.log + exit 1 + fi +fi + if [ "${install}" = "yes" ]; then echo "" echo "Installing ..." diff --git a/libusb/.private/ci-container-build.sh b/libusb/.private/ci-container-build.sh index 7419cb7..aa34941 100755 --- a/libusb/.private/ci-container-build.sh +++ b/libusb/.private/ci-container-build.sh @@ -50,6 +50,8 @@ CFLAGS+=" -Wredundant-decls" CFLAGS+=" -Wswitch-enum" export CFLAGS +export CXXFLAGS="\${CFLAGS}" + echo "" echo "Configuring ..." /source/configure --enable-examples-build --enable-tests-build @@ -61,11 +63,5 @@ make -j4 -k echo "" echo "Running umockdev tests ..." tests/umockdev - -echo "Running stress tests ..." -tests/stress -tests/stress_mt EOG EOF - - diff --git a/libusb/.private/wbs.txt b/libusb/.private/wbs.txt index 0e4a76b..c6a7e82 100644 --- a/libusb/.private/wbs.txt +++ b/libusb/.private/wbs.txt @@ -39,5 +39,5 @@ o Additional information: http://windows.libusb.info/#Driver_Installation - The MinGW and MS generated DLLs are fully interchangeable, provided that you use the import libs provided or generate one from the .def also provided. - - If you find any issue, please visit http://libusb.info/ and check the + - If you find any issue, please visit https://libusb.info/ and check the Support section diff --git a/libusb/AUTHORS b/libusb/AUTHORS index 8f91512..50b9c5e 100644 --- a/libusb/AUTHORS +++ b/libusb/AUTHORS @@ -13,15 +13,18 @@ Copyright © 2013-2018 Chris Dickens Other contributors: Aaron Luft Adam Korcz +Addison Crump Adrian Bunk Adrien Destugues Akshay Jaggi Alan Ott Alan Stern Aleksandr Mezin +Alexander Mot Alexander Pyhalov Alexander Schlarb Alexander Stein +Alex Feinman Alex Vatchenko Andrew Aldridge Andrew Fernandes @@ -44,6 +47,7 @@ Bence Csokas Benjamin Berg Benjamin Dobell Bohdan Tymkiv +Brad Smith Brent Rector Bruno Harbulot Carl Karsten @@ -59,7 +63,9 @@ David Moore Dmitry Fleytman Dmitry Kostjuchenko Dmitry Zakablukov +Dominik Boehi Doug Johnston +Edgar Fuß Evan Hunter Evan Miller Fabrice Fontaine @@ -68,6 +74,7 @@ Felipe Balbi Florian Albrechtskirchinger Francesco Montorsi Francisco Facioni +Francis Hart Frank Li Frederik Carlier Freek Dijkstra @@ -84,6 +91,7 @@ Ido Yariv Igor Anokhin Ihor Dutchak Ilya Konstantinov +Ingvar Stepanyan Jakub Klama James Hanko Jeffrey Nichols @@ -98,6 +106,7 @@ Joost Muller Josh Gao Joshua Blake Joshua Hou +Joshua M. Clulow Juan Cruz Viotti Julian Scheel Justin Bischoff @@ -112,11 +121,13 @@ Lars Wirzenius Lei Chen Léo Lam Liang Yunwang +Lonnie Abelbeck Luca Longinotti Luz Paz Mac Wang Marco Trevisan (Treviño) Marcus Meissner +Mario Kleiner Mark Kuo Markus Heidelberg Martin Ettl @@ -136,20 +147,26 @@ Moritz Fischer Nancy Li Nia Alarie Nicholas Corgan +Niklas Gürtler Omri Iluz +Orhan aib Kavrakoglu Orin Eman Ozkan Sezer +Pablo Prietz Patrick Stewart Paul Cercueil Paul Fertser Paul Qureshi Pekka Nikander +Petr Pazourek Philémon Favrod Pino Toscano Rob Walker Romain Vimont Roman Kalashnikov +Rosen Penev Ryan Hileman +Ryan Metcalfe Ryan Schmidt Saleem Rashid Sameeh Jubran @@ -158,6 +175,7 @@ Sebastian Pipping Sebastian von Ohr Sergey Serb Shawn Hoffman +Simon Chan Simon Haggett Simon Newton Slash Gordon @@ -165,6 +183,7 @@ Stefan Agner Stefan Tauner Steinar H. Gunderson Stephen Groat +Sylvain Fasel Theo Buehler Thomas Röfer Tim Hutt @@ -187,6 +206,7 @@ William Orr William Skellenger Xiaofan Chen Yegor Yefremov +Zeng Guang Zhiqiang Liu Zoltán Kovács Сергей Валерьевич @@ -199,4 +219,6 @@ parafin RipleyTom Seneral saur0n +SomeAlphabetGuy winterrace +xloem diff --git a/libusb/ChangeLog b/libusb/ChangeLog index 326a9b3..3db5baa 100644 --- a/libusb/ChangeLog +++ b/libusb/ChangeLog @@ -1,5 +1,22 @@ -For detailed information about the changes below, please see the git log or -visit: http://log.libusb.info +For detailed information about the changes below, please see the git log +or visit: http://log.libusb.info + +2024-01-31: v1.0.27 +* New libusb_init_context API to replace libusb_init +* New libusb_get_max_alt_packet_size API +* New libusb_get_platform_descriptor API (BOS) +* Allow setting log callback with libusb_set_option/libusb_init_context +* New WebAssembly + WebUSB backend using Emscripten +* Fix regression in libusb_set_interface_alt_setting +* Fix sync transfer completion race and use-after-free +* Fix hotplug exit ordering +* Linux: NO_DEVICE_DISCOVERY option set per context +* macOS: Fix missing device list cleanup locking +* macOS: Do not clear device data toggle for newer OS versions +* macOS: Fix running binaries on older OS than build host +* Windows: Allow claiming multiple associated interfaces +* Windows: Ignore non-configured devices instead of waiting +* Windows: Improved root hub detection 2022-04-10: v1.0.26 * Fix regression with transfer free's after closing device diff --git a/libusb/HACKING b/libusb/HACKING new file mode 100644 index 0000000..2961163 --- /dev/null +++ b/libusb/HACKING @@ -0,0 +1,25 @@ +Contributing to libusb +********************** + +For larger changes or API changes/extensions it may be wise to first +discuss on the mailing list or in the issue tracker before larger +coding efforts are initiated. + +If you extend or change the API make sure documentation is updated. +Please run make -C doc and check for any Doxygen warnings. + +Commit messages should be formatted to 72 chars width and have a +free-standing summary line. See for instance "Commit Guidelines" on +https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project +or https://cbea.ms/git-commit/ about how to make well-formed commit +messages. + +Put detailed information in the commit message itself, which will end +up in the git history. On the other hand the description that you fill +in the GitHub pull request web page does not go anywhere. + +For copyright reasons it is preferable to have your full name in the +commit author field. Do not update the AUTHOR file yourself, the +maintainers will update it as part of the release preparation. + +Please don't touch version_nano.h in your patches or pull requests. diff --git a/libusb/NEWS b/libusb/NEWS index 07695ab..ba2a610 100644 --- a/libusb/NEWS +++ b/libusb/NEWS @@ -1,2 +1,2 @@ -For the latest libusb news, please refer to the ChangeLog file, or visit: -http://libusb.info +For the latest libusb news, please refer to the ChangeLog file, or visit: +https://libusb.info diff --git a/libusb/README b/libusb/README index bf24733..c25266a 100644 --- a/libusb/README +++ b/libusb/README @@ -16,7 +16,7 @@ be ported to other operating systems. Please see the [PORTING](PORTING) file for more information. libusb homepage: -http://libusb.info/ +https://libusb.info/ Developers will wish to consult the API documentation: http://api.libusb.info diff --git a/libusb/README.git b/libusb/README.git index d67d374..b845138 100644 --- a/libusb/README.git +++ b/libusb/README.git @@ -11,7 +11,7 @@ configure with a default set of options, and will therefore generate a Makefile, whereas the latter does not invoke configure at all. If using autogen.sh, note that you can also append options, that will be passed as is to configure. -OS X-specific notes: +macOS-specific notes: ------------------- Starting with Xcode 4.3, neither Xcode.app nor the Xcode 'command line tools' diff --git a/libusb/Xcode/common.xcconfig b/libusb/Xcode/common.xcconfig index 54e7fb2..06108ad 100644 --- a/libusb/Xcode/common.xcconfig +++ b/libusb/Xcode/common.xcconfig @@ -1,7 +1,7 @@ // // libusb Xcode configuration file // Copyright © 2012 Pete Batard -// For more information, please visit: +// For more information, please visit: // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public @@ -26,6 +26,13 @@ ALWAYS_SEARCH_USER_PATHS = NO // Enable weak references for Objective-C CLANG_ENABLE_OBJC_WEAK = YES +// Allocate even uninitialized global variables in the data section of the object file, rather than generating them as common blocks. This has the effect that if the same variable is declared (without 'extern') in two different compilations, you will get an error when you link them. +GCC_NO_COMMON_BLOCKS = YES + +// Keep private symbols private. The first setting is -fvisibility=hidden, the second is -fvisibility-inlines-hidden. +GCC_SYMBOLS_PRIVATE_EXTERN = YES +GCC_INLINES_ARE_PRIVATE_EXTERN = YES + // Compiler errors. GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES diff --git a/libusb/Xcode/debug.xcconfig b/libusb/Xcode/debug.xcconfig index e910ee8..ca8ce3d 100644 --- a/libusb/Xcode/debug.xcconfig +++ b/libusb/Xcode/debug.xcconfig @@ -1,7 +1,7 @@ // // libusb Xcode configuration file // Copyright © 2012 Pete Batard -// For more information, please visit: +// For more information, please visit: // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public @@ -27,3 +27,6 @@ GCC_OPTIMIZATION_LEVEL = 0 // GCC_PREPROCESSOR_DEFINITIONS = $(inherited) DEBUG=1 + +// No need for Universal Binaries in debug. +ONLY_ACTIVE_ARCH = YES diff --git a/libusb/Xcode/libusb.xcconfig b/libusb/Xcode/libusb.xcconfig index 12f0c46..a41eab5 100644 --- a/libusb/Xcode/libusb.xcconfig +++ b/libusb/Xcode/libusb.xcconfig @@ -1,7 +1,7 @@ // // libusb Xcode configuration file // Copyright © 2012 Pete Batard -// For more information, please visit: +// For more information, please visit: // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public diff --git a/libusb/Xcode/libusb.xcodeproj/project.pbxproj b/libusb/Xcode/libusb.xcodeproj/project.pbxproj index 759a102..4fe9462 100644 --- a/libusb/Xcode/libusb.xcodeproj/project.pbxproj +++ b/libusb/Xcode/libusb.xcodeproj/project.pbxproj @@ -272,6 +272,36 @@ 1443EE8716417E63007E0579 /* libusb.xcconfig */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = text.xcconfig; path = libusb.xcconfig; sourceTree = SOURCE_ROOT; tabWidth = 4; usesTabs = 1; }; 1443EE8816417E63007E0579 /* release.xcconfig */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = text.xcconfig; path = release.xcconfig; sourceTree = SOURCE_ROOT; tabWidth = 4; usesTabs = 1; }; 1443EE8916417EA6007E0579 /* libusb_release.xcconfig */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = text.xcconfig; path = libusb_release.xcconfig; sourceTree = SOURCE_ROOT; tabWidth = 4; usesTabs = 1; }; + 14EC13E12B3D5BA600CF9AD0 /* emscripten_webusb.cpp */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.cpp.cpp; path = emscripten_webusb.cpp; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 14EC13E22B3D5BBE00CF9AD0 /* sunos_usb.h */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.h; path = sunos_usb.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 14EC13E32B3D5BBE00CF9AD0 /* openbsd_usb.c */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = openbsd_usb.c; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 14EC13E42B3D5BBE00CF9AD0 /* netbsd_usb.c */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = netbsd_usb.c; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 14EC13E52B3D5BBE00CF9AD0 /* events_windows.c */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = events_windows.c; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 14EC13E62B3D5BBE00CF9AD0 /* haiku_usb_raw.h */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.h; path = haiku_usb_raw.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 14EC13E72B3D5BBE00CF9AD0 /* linux_netlink.c */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = linux_netlink.c; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 14EC13E82B3D5BBE00CF9AD0 /* haiku_usb_backend.cpp */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.cpp.cpp; path = haiku_usb_backend.cpp; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 14EC13E92B3D5BBE00CF9AD0 /* haiku_usb_raw.cpp */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.cpp.cpp; path = haiku_usb_raw.cpp; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 14EC13EA2B3D5BBE00CF9AD0 /* linux_usbfs.h */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.h; path = linux_usbfs.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 14EC13EB2B3D5BBE00CF9AD0 /* sunos_usb.c */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = sunos_usb.c; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 14EC13EC2B3D5BBE00CF9AD0 /* linux_udev.c */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = linux_udev.c; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 14EC13ED2B3D5BBE00CF9AD0 /* haiku_usb.h */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.h; path = haiku_usb.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 14EC13EE2B3D5BBE00CF9AD0 /* events_windows.h */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.h; path = events_windows.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 14EC13EF2B3D5BBE00CF9AD0 /* null_usb.c */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = null_usb.c; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 14EC13F02B3D5BBE00CF9AD0 /* haiku_pollfs.cpp */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.cpp.cpp; path = haiku_pollfs.cpp; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 14EC13F12B3D5BBE00CF9AD0 /* linux_usbfs.c */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = linux_usbfs.c; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 14EC13F22B3D5BC800CF9AD0 /* windows_common.h */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.h; path = windows_common.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 14EC13F32B3D5BC800CF9AD0 /* threads_windows.c */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = threads_windows.c; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 14EC13F42B3D5BC800CF9AD0 /* windows_winusb.c */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = windows_winusb.c; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 14EC13F52B3D5BC800CF9AD0 /* windows_common.c */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = windows_common.c; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 14EC13F62B3D5BC800CF9AD0 /* windows_winusb.h */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.h; path = windows_winusb.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 14EC13F72B3D5BC800CF9AD0 /* threads_windows.h */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.h; path = threads_windows.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 14EC13F82B3D5BC800CF9AD0 /* windows_usbdk.h */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.h; path = windows_usbdk.h; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 14EC13F92B3D5BC800CF9AD0 /* windows_usbdk.c */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = windows_usbdk.c; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 1472E1592B43D66B00850BA3 /* init_context.c */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = init_context.c; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 1472E15A2B43D68600850BA3 /* stress_mt.c */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = stress_mt.c; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 1472E15D2B43D68600850BA3 /* macos.c */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = macos.c; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 1472E15F2B43D68600850BA3 /* set_option.c */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = set_option.c; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; + 1472E1602B43D69800850BA3 /* umockdev.c */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = umockdev.c; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; 2018D95E24E453BA001589B2 /* events_posix.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = events_posix.c; sourceTree = ""; }; 2018D96024E453D0001589B2 /* events_posix.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = events_posix.h; sourceTree = ""; }; 20468D67243298AE00650534 /* sam3u_benchmark */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = sam3u_benchmark; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -371,9 +401,14 @@ 001B1F09236C836000F231DC /* tests */ = { isa = PBXGroup; children = ( + 1472E1592B43D66B00850BA3 /* init_context.c */, 008A23CA236C849A004854AA /* libusb_testlib.h */, + 1472E15D2B43D68600850BA3 /* macos.c */, + 1472E15F2B43D68600850BA3 /* set_option.c */, + 1472E15A2B43D68600850BA3 /* stress_mt.c */, 008A23C6236C8445004854AA /* stress.c */, 008A23CB236C849A004854AA /* testlib.c */, + 1472E1602B43D69800850BA3 /* umockdev.c */, ); name = tests; path = ../tests; @@ -433,10 +468,35 @@ children = ( 008FBF6C1628B7E800BC5BE2 /* darwin_usb.c */, 008FBF6D1628B7E800BC5BE2 /* darwin_usb.h */, + 14EC13E12B3D5BA600CF9AD0 /* emscripten_webusb.cpp */, 2018D95E24E453BA001589B2 /* events_posix.c */, 2018D96024E453D0001589B2 /* events_posix.h */, + 14EC13E52B3D5BBE00CF9AD0 /* events_windows.c */, + 14EC13EE2B3D5BBE00CF9AD0 /* events_windows.h */, + 14EC13F02B3D5BBE00CF9AD0 /* haiku_pollfs.cpp */, + 14EC13E82B3D5BBE00CF9AD0 /* haiku_usb_backend.cpp */, + 14EC13E92B3D5BBE00CF9AD0 /* haiku_usb_raw.cpp */, + 14EC13E62B3D5BBE00CF9AD0 /* haiku_usb_raw.h */, + 14EC13ED2B3D5BBE00CF9AD0 /* haiku_usb.h */, + 14EC13E72B3D5BBE00CF9AD0 /* linux_netlink.c */, + 14EC13EC2B3D5BBE00CF9AD0 /* linux_udev.c */, + 14EC13F12B3D5BBE00CF9AD0 /* linux_usbfs.c */, + 14EC13EA2B3D5BBE00CF9AD0 /* linux_usbfs.h */, + 14EC13E42B3D5BBE00CF9AD0 /* netbsd_usb.c */, + 14EC13EF2B3D5BBE00CF9AD0 /* null_usb.c */, + 14EC13E32B3D5BBE00CF9AD0 /* openbsd_usb.c */, + 14EC13EB2B3D5BBE00CF9AD0 /* sunos_usb.c */, + 14EC13E22B3D5BBE00CF9AD0 /* sunos_usb.h */, 008FBF741628B7E800BC5BE2 /* threads_posix.c */, 008FBF751628B7E800BC5BE2 /* threads_posix.h */, + 14EC13F32B3D5BC800CF9AD0 /* threads_windows.c */, + 14EC13F72B3D5BC800CF9AD0 /* threads_windows.h */, + 14EC13F52B3D5BC800CF9AD0 /* windows_common.c */, + 14EC13F22B3D5BC800CF9AD0 /* windows_common.h */, + 14EC13F92B3D5BC800CF9AD0 /* windows_usbdk.c */, + 14EC13F82B3D5BC800CF9AD0 /* windows_usbdk.h */, + 14EC13F42B3D5BC800CF9AD0 /* windows_winusb.c */, + 14EC13F62B3D5BC800CF9AD0 /* windows_winusb.h */, ); path = os; sourceTree = ""; @@ -1050,18 +1110,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 1443EE8516417E63007E0579 /* debug.xcconfig */; buildSettings = { - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_UNDECLARED_SELECTOR = YES; - ONLY_ACTIVE_ARCH = YES; }; name = Debug; }; @@ -1069,16 +1117,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 1443EE8816417E63007E0579 /* release.xcconfig */; buildSettings = { - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_UNDECLARED_SELECTOR = YES; }; name = Release; }; @@ -1086,10 +1124,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 1443EE8616417E63007E0579 /* libusb_debug.xcconfig */; buildSettings = { - OTHER_CFLAGS = ( - "-fvisibility=hidden", - "-pthread", - ); }; name = Debug; }; @@ -1097,10 +1131,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 1443EE8916417EA6007E0579 /* libusb_release.xcconfig */; buildSettings = { - OTHER_CFLAGS = ( - "-fvisibility=hidden", - "-pthread", - ); }; name = Release; }; diff --git a/libusb/Xcode/libusb_debug.xcconfig b/libusb/Xcode/libusb_debug.xcconfig index 11bc4c3..bc2c08c 100644 --- a/libusb/Xcode/libusb_debug.xcconfig +++ b/libusb/Xcode/libusb_debug.xcconfig @@ -1,7 +1,7 @@ // // libusb Xcode configuration file // Copyright © 2012 Pete Batard -// For more information, please visit: +// For more information, please visit: // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public diff --git a/libusb/Xcode/libusb_release.xcconfig b/libusb/Xcode/libusb_release.xcconfig index 3bd47bb..10a339a 100644 --- a/libusb/Xcode/libusb_release.xcconfig +++ b/libusb/Xcode/libusb_release.xcconfig @@ -1,7 +1,7 @@ // // libusb Xcode configuration file // Copyright © 2012 Pete Batard -// For more information, please visit: +// For more information, please visit: // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public diff --git a/libusb/Xcode/release.xcconfig b/libusb/Xcode/release.xcconfig index ead284a..760df51 100644 --- a/libusb/Xcode/release.xcconfig +++ b/libusb/Xcode/release.xcconfig @@ -1,7 +1,7 @@ // // libusb Xcode configuration file // Copyright © 2012 Pete Batard -// For more information, please visit: +// For more information, please visit: // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public diff --git a/libusb/configure.ac b/libusb/configure.ac index d5b50ab..6dc7c69 100644 --- a/libusb/configure.ac +++ b/libusb/configure.ac @@ -16,7 +16,7 @@ LU_DEFINE_VERSION_ATOM([LIBUSB_MICRO]) LU_DEFINE_VERSION_RC_ATOM([LIBUSB_RC]) AC_PREREQ([2.69]) -AC_INIT([libusb-1.0], [LIBUSB_MAJOR[.]LIBUSB_MINOR[.]LIBUSB_MICRO[]LIBUSB_RC], [libusb-devel@lists.sourceforge.net], [libusb-1.0], [http://libusb.info]) +AC_INIT([libusb-1.0], [LIBUSB_MAJOR[.]LIBUSB_MINOR[.]LIBUSB_MICRO[]LIBUSB_RC], [libusb-devel@lists.sourceforge.net], [libusb-1.0], [https://libusb.info]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_SRCDIR([libusb/core.c]) AC_CONFIG_MACRO_DIR([m4]) @@ -31,9 +31,9 @@ dnl Library versioning dnl These numbers should be tweaked on every release. Read carefully: dnl http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html dnl http://sourceware.org/autobook/autobook/autobook_91.html -lt_current=3 +lt_current=4 lt_revision=0 -lt_age=3 +lt_age=4 LT_LDFLAGS="-version-info ${lt_current}:${lt_revision}:${lt_age} -no-undefined" m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) @@ -83,11 +83,17 @@ case $host in backend=haiku platform=posix ;; -wasm32-**) +wasm*-emscripten) AC_MSG_RESULT([Emscripten]) backend=emscripten platform=posix ;; +wasm*-unknown-none) + AC_MSG_ERROR([ +--host=$host_alias is not accepted as it might become ambiguous in the future. +Please use an explicit --host=$host_cpu-emscripten instead. + ]) + ;; *-linux* | *-uclinux*) dnl on Android Linux, some functions are in different places case $host in @@ -227,9 +233,10 @@ windows) LT_LDFLAGS="${LT_LDFLAGS} -avoid-version" ;; emscripten) - AC_SUBST(EXEEXT, [.html]) # Note: LT_LDFLAGS is not enough here because we need link flags for executable. - AM_LDFLAGS="${AM_LDFLAGS} --bind -s ASYNCIFY -s ASSERTIONS -s ALLOW_MEMORY_GROWTH -s INVOKE_RUN=0 -s EXPORTED_RUNTIME_METHODS=['callMain']" + EM_LDFLAGS="--bind -s ASYNCIFY" + AM_LDFLAGS="${AM_LDFLAGS} ${EM_LDFLAGS} -s ASSERTIONS -s ALLOW_MEMORY_GROWTH" + LIBS="${LIBS} ${EM_LDFLAGS}" ;; *) dnl no special handling required @@ -376,7 +383,7 @@ AC_ARG_ENABLE([tests-build], AM_CONDITIONAL([BUILD_EXAMPLES], [test "x$build_examples" != xno]) AM_CONDITIONAL([BUILD_TESTS], [test "x$build_tests" != xno]) -AM_CONDITIONAL([BUILD_UMOCKDEV_TEST], [test "x$ac_have_umockdev" = xyes -a "x$log_enabled" != xno]) +AM_CONDITIONAL([BUILD_UMOCKDEV_TEST], [test "x$ac_have_umockdev" = xyes -a "x$log_enabled" != xno -a "x$debug_log_enabled" != xyes]) AM_CONDITIONAL([CREATE_IMPORT_LIB], [test "x$create_import_lib" = xyes]) AM_CONDITIONAL([OS_DARWIN], [test "x$backend" = xdarwin]) AM_CONDITIONAL([OS_HAIKU], [test "x$backend" = xhaiku]) diff --git a/libusb/examples/hotplugtest.c b/libusb/examples/hotplugtest.c index 98b01ee..3e092cf 100644 --- a/libusb/examples/hotplugtest.c +++ b/libusb/examples/hotplugtest.c @@ -37,12 +37,14 @@ static int LIBUSB_CALL hotplug_callback(libusb_context *ctx, libusb_device *dev, (void)user_data; rc = libusb_get_device_descriptor(dev, &desc); - if (LIBUSB_SUCCESS != rc) { - fprintf (stderr, "Error getting device descriptor\n"); + if (LIBUSB_SUCCESS == rc) { + printf ("Device attached: %04x:%04x\n", desc.idVendor, desc.idProduct); + } else { + printf ("Device attached\n"); + fprintf (stderr, "Error getting device descriptor: %s\n", + libusb_strerror((enum libusb_error)rc)); } - printf ("Device attached: %04x:%04x\n", desc.idVendor, desc.idProduct); - if (handle) { libusb_close (handle); handle = NULL; @@ -50,7 +52,8 @@ static int LIBUSB_CALL hotplug_callback(libusb_context *ctx, libusb_device *dev, rc = libusb_open (dev, &handle); if (LIBUSB_SUCCESS != rc) { - fprintf (stderr, "Error opening device\n"); + fprintf (stderr, "No access to device: %s\n", + libusb_strerror((enum libusb_error)rc)); } done++; @@ -60,12 +63,22 @@ static int LIBUSB_CALL hotplug_callback(libusb_context *ctx, libusb_device *dev, static int LIBUSB_CALL hotplug_callback_detach(libusb_context *ctx, libusb_device *dev, libusb_hotplug_event event, void *user_data) { + struct libusb_device_descriptor desc; + int rc; + (void)ctx; (void)dev; (void)event; (void)user_data; - printf ("Device detached\n"); + rc = libusb_get_device_descriptor(dev, &desc); + if (LIBUSB_SUCCESS == rc) { + printf ("Device detached: %04x:%04x\n", desc.idVendor, desc.idProduct); + } else { + printf ("Device detached\n"); + fprintf (stderr, "Error getting device descriptor: %s\n", + libusb_strerror((enum libusb_error)rc)); + } if (handle) { libusb_close (handle); @@ -83,14 +96,15 @@ int main(int argc, char *argv[]) int product_id, vendor_id, class_id; int rc; - vendor_id = (argc > 1) ? (int)strtol (argv[1], NULL, 0) : 0x045a; - product_id = (argc > 2) ? (int)strtol (argv[2], NULL, 0) : 0x5005; + vendor_id = (argc > 1) ? (int)strtol (argv[1], NULL, 0) : LIBUSB_HOTPLUG_MATCH_ANY; + product_id = (argc > 2) ? (int)strtol (argv[2], NULL, 0) : LIBUSB_HOTPLUG_MATCH_ANY; class_id = (argc > 3) ? (int)strtol (argv[3], NULL, 0) : LIBUSB_HOTPLUG_MATCH_ANY; rc = libusb_init_context(/*ctx=*/NULL, /*options=*/NULL, /*num_options=*/0); - if (rc < 0) + if (LIBUSB_SUCCESS != rc) { - printf("failed to initialise libusb: %s\n", libusb_error_name(rc)); + printf ("failed to initialise libusb: %s\n", + libusb_strerror((enum libusb_error)rc)); return EXIT_FAILURE; } @@ -118,8 +132,9 @@ int main(int argc, char *argv[]) while (done < 2) { rc = libusb_handle_events (NULL); - if (rc < 0) - printf("libusb_handle_events() failed: %s\n", libusb_error_name(rc)); + if (LIBUSB_SUCCESS != rc) + printf ("libusb_handle_events() failed: %s\n", + libusb_strerror((enum libusb_error)rc)); } if (handle) { diff --git a/libusb/examples/xusb.c b/libusb/examples/xusb.c index 441af01..239450c 100644 --- a/libusb/examples/xusb.c +++ b/libusb/examples/xusb.c @@ -1035,7 +1035,6 @@ static int test_device(uint16_t vid, uint16_t pid) int main(int argc, char** argv) { - static char debug_env_str[] = "LIBUSB_DEBUG=4"; // LIBUSB_LOG_LEVEL_DEBUG bool show_help = false; bool debug_mode = false; const struct libusb_version* version; @@ -1159,17 +1158,17 @@ int main(int argc, char** argv) return 0; } - // xusb is commonly used as a debug tool, so it's convenient to have debug output during libusb_init(), - // but since we can't call on libusb_set_option() before libusb_init(), we use the env variable method - old_dbg_str = getenv("LIBUSB_DEBUG"); + version = libusb_get_version(); + printf("Using libusb v%d.%d.%d.%d\n\n", version->major, version->minor, version->micro, version->nano); + + // xusb is commonly used as a debug tool, so it's convenient to have debug output during libusb_init_context(). if (debug_mode) { - if (putenv(debug_env_str) != 0) - printf("Unable to set debug level\n"); + const struct libusb_init_option options = {.option = LIBUSB_OPTION_LOG_LEVEL, .value = {.ival = LIBUSB_LOG_LEVEL_DEBUG}}; + r = libusb_init_context(/*ctx=*/NULL, /*options=*/&options, /*num_options=*/1); + } else { + r = libusb_init_context(/*ctx=*/NULL, /*options=*/NULL, /*num_options=*/0); } - version = libusb_get_version(); - printf("Using libusb v%d.%d.%d.%d\n\n", version->major, version->minor, version->micro, version->nano); - r = libusb_init_context(/*ctx=*/NULL, /*options=*/NULL, /*num_options=*/0); if (r < 0) return r; diff --git a/libusb/libusb/Makefile.am b/libusb/libusb/Makefile.am index 30d3547..640edd8 100644 --- a/libusb/libusb/Makefile.am +++ b/libusb/libusb/Makefile.am @@ -34,8 +34,10 @@ if OS_DARWIN OS_SRC = $(OS_DARWIN_SRC) endif +noinst_LTLIBRARIES = + if OS_HAIKU -noinst_LTLIBRARIES = libusb_haiku.la +noinst_LTLIBRARIES += libusb_haiku.la libusb_haiku_la_SOURCES = $(OS_HAIKU_SRC) libusb_1_0_la_LIBADD = libusb_haiku.la endif @@ -50,8 +52,9 @@ endif endif if OS_EMSCRIPTEN -noinst_LTLIBRARIES = libusb_emscripten.la +noinst_LTLIBRARIES += libusb_emscripten.la libusb_emscripten_la_SOURCES = $(OS_EMSCRIPTEN_SRC) +AM_CXXFLAGS += -std=c++20 libusb_1_0_la_LIBADD = libusb_emscripten.la endif diff --git a/libusb/libusb/core.c b/libusb/libusb/core.c index b527d18..ffe33b7 100644 --- a/libusb/libusb/core.c +++ b/libusb/libusb/core.c @@ -34,7 +34,7 @@ static const struct libusb_version libusb_version_internal = { LIBUSB_MAJOR, LIBUSB_MINOR, LIBUSB_MICRO, LIBUSB_NANO, - LIBUSB_RC, "http://libusb.info" }; + LIBUSB_RC, "https://libusb.info" }; static struct timespec timestamp_origin; #if defined(ENABLE_LOGGING) && !defined(USE_SYSTEM_LOGGING_FACILITY) static libusb_log_cb log_handler; @@ -43,6 +43,9 @@ static libusb_log_cb log_handler; struct libusb_context *usbi_default_context; struct libusb_context *usbi_fallback_context; static int default_context_refcnt; +#if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING) +static usbi_atomic_t default_debug_level = -1; +#endif static usbi_mutex_static_t default_context_lock = USBI_MUTEX_INITIALIZER; static struct usbi_option default_context_options[LIBUSB_OPTION_MAX]; @@ -57,12 +60,12 @@ struct list_head active_contexts_list; * * libusb is an open source library that allows you to communicate with USB * devices from user space. For more info, see the - * libusb homepage. + * libusb homepage. * * This documentation is aimed at application developers wishing to * communicate with USB peripherals from their own software. After reviewing * this documentation, feedback and questions can be sent to the - * libusb-devel mailing list. + * libusb-devel mailing list. * * This documentation assumes knowledge of how to operate USB devices from * a software standpoint (descriptors, configurations, interfaces, endpoints, @@ -333,18 +336,18 @@ if (cfg != desired) * libusb after one of them calls libusb_exit(), etc. * * This is made possible through libusb's context concept. When you - * call libusb_init(), you are (optionally) given a context. You can then pass + * call libusb_init_context(), you are (optionally) given a context. You can then pass * this context pointer back into future libusb functions. * * In order to keep things simple for more simplistic applications, it is * legal to pass NULL to all functions requiring a context pointer (as long as * you're sure no other code will attempt to use libusb from the same process). * When you pass NULL, the default context will be used. The default context - * is created the first time a process calls libusb_init() when no other + * is created the first time a process calls libusb_init_context() when no other * context is alive. Contexts are destroyed during libusb_exit(). * * The default context is reference-counted and can be shared. That means that - * if libusb_init(NULL) is called twice within the same process, the two + * if libusb_init_context(NULL, x, y) is called twice within the same process, the two * users end up sharing the same context. The deinitialization and freeing of * the default context will only happen when the last user calls libusb_exit(). * In other words, the default context is created and initialized when its @@ -934,7 +937,7 @@ uint8_t API_EXPORTED libusb_get_port_number(libusb_device *dev) /** \ingroup libusb_dev * Get the list of all port numbers from root for the specified device * - * Since version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102 + * Since version 1.0.16, \ref LIBUSBX_API_VERSION >= 0x01000102 * \param dev a device * \param port_numbers the array that should contain the port numbers * \param port_numbers_len the maximum length of the array. As per the USB 3.0 @@ -1221,7 +1224,7 @@ int API_EXPORTED libusb_get_max_iso_packet_size(libusb_device *dev, * libusb_set_iso_packet_lengths() in order to set the length field of every * isochronous packet in a transfer. * - * Since v1.0.27. + * Since version 1.0.27, \ref LIBUSB_API_VERSION >= 0x0100010A * * \param dev a device * \param interface_number the bInterfaceNumber of the interface @@ -2202,21 +2205,12 @@ int API_EXPORTED libusb_set_auto_detach_kernel_driver( } /** \ingroup libusb_lib - * \deprecated Use libusb_set_option() or libusb_init_context() instead - * using the \ref LIBUSB_OPTION_LOG_LEVEL option. + * Deprecated. Use libusb_set_option() or libusb_init_context() instead, + * with the \ref LIBUSB_OPTION_LOG_LEVEL option. */ void API_EXPORTED libusb_set_debug(libusb_context *ctx, int level) { -#if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING) - ctx = usbi_get_context(ctx); - if (!ctx->debug_fixed) { - level = CLAMP(level, LIBUSB_LOG_LEVEL_NONE, LIBUSB_LOG_LEVEL_DEBUG); - ctx->debug = (enum libusb_log_level)level; - } -#else - UNUSED(ctx); - UNUSED(level); -#endif + libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, level); } static void libusb_set_log_cb_internal(libusb_context *ctx, libusb_log_cb cb, @@ -2300,6 +2294,9 @@ int API_EXPORTEDV libusb_set_option(libusb_context *ctx, int arg = 0, r = LIBUSB_SUCCESS; libusb_log_cb log_cb = NULL; va_list ap; +#if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING) + int is_default_context = (NULL == ctx); +#endif va_start(ap, option); @@ -2330,28 +2327,29 @@ int API_EXPORTEDV libusb_set_option(libusb_context *ctx, default_context_options[option].arg.ival = arg; } else if (LIBUSB_OPTION_LOG_CB == option) { default_context_options[option].arg.log_cbval = log_cb; + libusb_set_log_cb_internal(NULL, log_cb, LIBUSB_LOG_CB_GLOBAL); } usbi_mutex_static_unlock(&default_context_lock); } ctx = usbi_get_context(ctx); - if (NULL == ctx) { - libusb_set_log_cb_internal(NULL, log_cb, LIBUSB_LOG_CB_GLOBAL); + if (NULL == ctx) break; - } switch (option) { case LIBUSB_OPTION_LOG_LEVEL: #if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING) - if (!ctx->debug_fixed) + if (!ctx->debug_fixed) { ctx->debug = (enum libusb_log_level)arg; + if (is_default_context) + usbi_atomic_store(&default_debug_level, CLAMP(arg, LIBUSB_LOG_LEVEL_NONE, LIBUSB_LOG_LEVEL_DEBUG)); + } #endif break; /* Handle all backend-specific options here */ case LIBUSB_OPTION_USE_USBDK: case LIBUSB_OPTION_NO_DEVICE_DISCOVERY: - case LIBUSB_OPTION_WINUSB_RAW_IO: if (usbi_backend.set_option) { r = usbi_backend.set_option(ctx, option, ap); break; @@ -2363,6 +2361,8 @@ int API_EXPORTEDV libusb_set_option(libusb_context *ctx, case LIBUSB_OPTION_LOG_CB: libusb_set_log_cb_internal(ctx, log_cb, LIBUSB_LOG_CB_CONTEXT); break; + + case LIBUSB_OPTION_MAX: /* unreachable */ default: r = LIBUSB_ERROR_INVALID_PARAM; } @@ -2394,18 +2394,9 @@ static enum libusb_log_level get_env_debug_level(void) #endif /** \ingroup libusb_lib - * Initialize libusb. This function must be called before calling any other - * libusb function. - * - * If you do not provide an output location for a context pointer, a default - * context will be created. If there was already a default context, it will - * be reused (and nothing will be initialized/reinitialized). Deprecated in - * favor of \ref libusb_init_context(). + * Deprecated initialization function. Equivalent to calling libusb_init_context with no options. * - * \param ctx Optional output location for context pointer. - * Only valid on return code 0. - * \returns 0 on success, or a LIBUSB_ERROR code on failure - * \see libusb_contexts + * \see libusb_init_context */ int API_EXPORTED libusb_init(libusb_context **ctx) { @@ -2421,6 +2412,8 @@ int API_EXPORTED libusb_init(libusb_context **ctx) * be reused (and nothing will be initialized/reinitialized and options will * be ignored). If num_options is 0 then options is ignored and may be NULL. * + * Since version 1.0.27, \ref LIBUSB_API_VERSION >= 0x0100010A + * * \param ctx Optional output location for context pointer. * Only valid on return code 0. * \param options Optional array of options to set on the new context. @@ -2444,10 +2437,12 @@ int API_EXPORTED libusb_init_context(libusb_context **ctx, const struct libusb_i } /* check for first init */ + usbi_mutex_static_lock(&active_contexts_lock); if (!active_contexts_list.next) { list_init(&active_contexts_list); usbi_get_monotonic_time(×tamp_origin); } + usbi_mutex_static_unlock(&active_contexts_lock); _ctx = calloc(1, PTR_ALIGN(sizeof(*_ctx)) + priv_size); if (!_ctx) { @@ -2490,6 +2485,11 @@ int API_EXPORTED libusb_init_context(libusb_context **ctx, const struct libusb_i case LIBUSB_OPTION_LOG_CB: r = libusb_set_option(_ctx, options[i].option, options[i].value.log_cbval); break; + + case LIBUSB_OPTION_LOG_LEVEL: + case LIBUSB_OPTION_USE_USBDK: + case LIBUSB_OPTION_NO_DEVICE_DISCOVERY: + case LIBUSB_OPTION_MAX: default: r = libusb_set_option(_ctx, options[i].option, options[i].value.ival); } @@ -2501,6 +2501,9 @@ int API_EXPORTED libusb_init_context(libusb_context **ctx, const struct libusb_i if (!ctx) { usbi_default_context = _ctx; default_context_refcnt = 1; +#if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING) + usbi_atomic_store(&default_debug_level, _ctx->debug); +#endif usbi_dbg(usbi_default_context, "created default context"); } @@ -2528,6 +2531,10 @@ int API_EXPORTED libusb_init_context(libusb_context **ctx, const struct libusb_i *ctx = _ctx; if (!usbi_fallback_context) { +#if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING) + if (usbi_atomic_load(&default_debug_level) == -1) + usbi_atomic_store(&default_debug_level, _ctx->debug); +#endif usbi_fallback_context = _ctx; usbi_dbg(usbi_fallback_context, "installing new context as implicit default"); } @@ -2635,7 +2642,7 @@ void API_EXPORTED libusb_exit(libusb_context *ctx) /** \ingroup libusb_misc * Check at runtime if the loaded library has a given capability. - * This call should be performed after \ref libusb_init(), to ensure the + * This call should be performed after \ref libusb_init_context(), to ensure the * backend has updated its capability set. * * \param capability the \ref libusb_capability to check for @@ -2754,13 +2761,14 @@ static void log_v(struct libusb_context *ctx, enum libusb_log_level level, UNUSED(ctx); #else enum libusb_log_level ctx_level; + long default_level_value; - ctx = ctx ? ctx : usbi_default_context; - ctx = ctx ? ctx : usbi_fallback_context; - if (ctx) + if (ctx) { ctx_level = ctx->debug; - else - ctx_level = get_env_debug_level(); + } else { + default_level_value = usbi_atomic_load(&default_debug_level); + ctx_level = default_level_value < 0 ? get_env_debug_level() : (enum libusb_log_level)default_level_value; + } if (ctx_level < level) return; diff --git a/libusb/libusb/descriptor.c b/libusb/libusb/descriptor.c index 97e1d9a..4623ad1 100644 --- a/libusb/libusb/descriptor.c +++ b/libusb/libusb/descriptor.c @@ -521,7 +521,7 @@ static int get_config_descriptor(struct libusb_device *dev, uint8_t config_idx, * * This is a non-blocking function; the device descriptor is cached in memory. * - * Note since libusb-1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102, this + * Note since libusb-1.0.16, \ref LIBUSBX_API_VERSION >= 0x01000102, this * function always succeeds. * * \param dev the device @@ -1073,6 +1073,8 @@ void API_EXPORTED libusb_free_container_id_descriptor( /** \ingroup libusb_desc * Get a platform descriptor * + * Since version 1.0.27, \ref LIBUSB_API_VERSION >= 0x0100010A + * * \param ctx the context to operate on, or NULL for the default context * \param dev_cap Device Capability descriptor with a bDevCapabilityType of * \ref libusb_capability_type::LIBUSB_BT_PLATFORM_DESCRIPTOR @@ -1148,7 +1150,7 @@ int API_EXPORTED libusb_get_string_descriptor_ascii(libusb_device_handle *dev_ha uint8_t desc_index, unsigned char *data, int length) { union usbi_string_desc_buf str; - int r, si, di; + int r; uint16_t langid, wdata; /* Asking for the zero'th index is special - it returns a string @@ -1184,20 +1186,26 @@ int API_EXPORTED libusb_get_string_descriptor_ascii(libusb_device_handle *dev_ha else if ((str.desc.bLength & 1) || str.desc.bLength != r) usbi_warn(HANDLE_CTX(dev_handle), "suspicious bLength %u for string descriptor (read %d)", str.desc.bLength, r); - di = 0; - for (si = 2; si < str.desc.bLength; si += 2) { - if (di >= (length - 1)) - break; + /* Stop one byte before the end to leave room for null termination. */ + int dest_max = length - 1; + + /* The descriptor has this number of wide characters */ + int src_max = (str.desc.bLength - 1 - 1) / 2; - wdata = libusb_le16_to_cpu(str.desc.wData[di]); + /* Neither read nor write more than the smallest buffer */ + int idx_max = MIN(dest_max, src_max); + + int idx; + for (idx = 0; idx < idx_max; ++idx) { + wdata = libusb_le16_to_cpu(str.desc.wData[idx]); if (wdata < 0x80) - data[di++] = (unsigned char)wdata; + data[idx] = (unsigned char)wdata; else - data[di++] = '?'; /* non-ASCII */ + data[idx] = '?'; /* non-ASCII */ } - data[di] = 0; - return di; + data[idx] = 0; /* null-terminate string */ + return idx; } static int parse_iad_array(struct libusb_context *ctx, @@ -1220,6 +1228,11 @@ static int parse_iad_array(struct libusb_context *ctx, iad_array->length = 0; while (consumed < size) { parse_descriptor(buf, "bb", &header); + if (header.bLength < 2) { + usbi_err(ctx, "invalid descriptor bLength %d", + header.bLength); + return LIBUSB_ERROR_IO; + } if (header.bDescriptorType == LIBUSB_DT_INTERFACE_ASSOCIATION) iad_array->length++; buf += header.bLength; diff --git a/libusb/libusb/hotplug.c b/libusb/libusb/hotplug.c index 0b34e9f..3c64f69 100644 --- a/libusb/libusb/hotplug.c +++ b/libusb/libusb/hotplug.c @@ -33,7 +33,7 @@ * * \section hotplug_intro Introduction * - * Version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102, has added support + * Version 1.0.16, \ref LIBUSBX_API_VERSION >= 0x01000102, has added support * for hotplug events on some platforms (you should test if your platform * supports hotplug notification by calling \ref libusb_has_capability() with * parameter \ref LIBUSB_CAP_HAS_HOTPLUG). @@ -117,7 +117,7 @@ int main (void) { libusb_hotplug_callback_handle callback_handle; int rc; - libusb_init(NULL); + libusb_init_context(NULL, NULL, 0); rc = libusb_hotplug_register_callback(NULL, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, 0, 0x045a, 0x5005, diff --git a/libusb/libusb/io.c b/libusb/libusb/io.c index 36e0b81..ab84ba6 100644 --- a/libusb/libusb/io.c +++ b/libusb/libusb/io.c @@ -1244,8 +1244,8 @@ void usbi_io_exit(struct libusb_context *ctx) static void calculate_timeout(struct usbi_transfer *itransfer) { - unsigned int timeout = - USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer)->timeout; + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + unsigned int timeout = transfer->timeout; if (!timeout) { TIMESPEC_CLEAR(&itransfer->timeout); @@ -1289,30 +1289,25 @@ DEFAULT_VISIBILITY struct libusb_transfer * LIBUSB_CALL libusb_alloc_transfer( int iso_packets) { - size_t priv_size; - size_t alloc_size; - unsigned char *ptr; - struct usbi_transfer *itransfer; - struct libusb_transfer *transfer; - assert(iso_packets >= 0); if (iso_packets < 0) return NULL; - priv_size = PTR_ALIGN(usbi_backend.transfer_priv_size); - alloc_size = priv_size - + sizeof(struct usbi_transfer) - + sizeof(struct libusb_transfer) - + (sizeof(struct libusb_iso_packet_descriptor) * (size_t)iso_packets); - ptr = calloc(1, alloc_size); + size_t priv_size = PTR_ALIGN(usbi_backend.transfer_priv_size); + size_t usbi_transfer_size = PTR_ALIGN(sizeof(struct usbi_transfer)); + size_t libusb_transfer_size = PTR_ALIGN(sizeof(struct libusb_transfer)); + size_t iso_packets_size = sizeof(struct libusb_iso_packet_descriptor) * (size_t)iso_packets; + size_t alloc_size = priv_size + usbi_transfer_size + libusb_transfer_size + iso_packets_size; + unsigned char *ptr = calloc(1, alloc_size); if (!ptr) return NULL; - itransfer = (struct usbi_transfer *)(ptr + priv_size); + struct usbi_transfer *itransfer = (struct usbi_transfer *)(ptr + priv_size); itransfer->num_iso_packets = iso_packets; itransfer->priv = ptr; usbi_mutex_init(&itransfer->lock); - transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + return transfer; } @@ -1335,10 +1330,6 @@ struct libusb_transfer * LIBUSB_CALL libusb_alloc_transfer( */ void API_EXPORTED libusb_free_transfer(struct libusb_transfer *transfer) { - struct usbi_transfer *itransfer; - size_t priv_size; - unsigned char *ptr; - if (!transfer) return; @@ -1346,20 +1337,19 @@ void API_EXPORTED libusb_free_transfer(struct libusb_transfer *transfer) if (transfer->flags & LIBUSB_TRANSFER_FREE_BUFFER) free(transfer->buffer); - itransfer = LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer); + struct usbi_transfer *itransfer = LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer); usbi_mutex_destroy(&itransfer->lock); if (itransfer->dev) libusb_unref_device(itransfer->dev); - priv_size = PTR_ALIGN(usbi_backend.transfer_priv_size); - ptr = (unsigned char *)itransfer - priv_size; + unsigned char *ptr = USBI_TRANSFER_TO_TRANSFER_PRIV(itransfer); assert(ptr == itransfer->priv); free(ptr); } /* iterates through the flying transfers, and rearms the timer based on the * next upcoming timeout. - * must be called with flying_list locked. + * NB: flying_transfers_lock must be held when calling this. * returns 0 on success or a LIBUSB_ERROR code on failure. */ #ifdef HAVE_OS_TIMER @@ -1380,7 +1370,8 @@ static int arm_timer_for_next_timeout(struct libusb_context *ctx) /* act on first transfer that has not already been handled */ if (!(itransfer->timeout_flags & (USBI_TRANSFER_TIMEOUT_HANDLED | USBI_TRANSFER_OS_HANDLES_TIMEOUT))) { - usbi_dbg(ctx, "next timeout originally %ums", USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer)->timeout); + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + usbi_dbg(ctx, "next timeout originally %ums", transfer->timeout); return usbi_arm_timer(&ctx->timer, cur_ts); } } @@ -1398,7 +1389,8 @@ static inline int arm_timer_for_next_timeout(struct libusb_context *ctx) /* add a transfer to the (timeout-sorted) active transfers list. * This function will return non 0 if fails to update the timer, - * in which case the transfer is *not* on the flying_transfers list. */ + * in which case the transfer is *not* on the flying_transfers list. + * NB: flying_transfers_lock MUST be held when calling this. */ static int add_to_flying_list(struct usbi_transfer *itransfer) { struct usbi_transfer *cur; @@ -1442,8 +1434,9 @@ static int add_to_flying_list(struct usbi_transfer *itransfer) if (first && usbi_using_timer(ctx) && TIMESPEC_IS_SET(timeout)) { /* if this transfer has the lowest timeout of all active transfers, * rearm the timer with this transfer's timeout */ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); usbi_dbg(ctx, "arm timer for timeout in %ums (first in line)", - USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer)->timeout); + transfer->timeout); r = usbi_arm_timer(&ctx->timer, timeout); } #else @@ -1459,20 +1452,19 @@ static int add_to_flying_list(struct usbi_transfer *itransfer) /* remove a transfer from the active transfers list. * This function will *always* remove the transfer from the * flying_transfers list. It will return a LIBUSB_ERROR code - * if it fails to update the timer for the next timeout. */ + * if it fails to update the timer for the next timeout. + * NB: flying_transfers_lock MUST be held when calling this. */ static int remove_from_flying_list(struct usbi_transfer *itransfer) { struct libusb_context *ctx = ITRANSFER_CTX(itransfer); int rearm_timer; int r = 0; - usbi_mutex_lock(&ctx->flying_transfers_lock); rearm_timer = (TIMESPEC_IS_SET(&itransfer->timeout) && list_first_entry(&ctx->flying_transfers, struct usbi_transfer, list) == itransfer); list_del(&itransfer->list); if (rearm_timer) r = arm_timer_for_next_timeout(ctx); - usbi_mutex_unlock(&ctx->flying_transfers_lock); return r; } @@ -1566,8 +1558,11 @@ int API_EXPORTED libusb_submit_transfer(struct libusb_transfer *transfer) } usbi_mutex_unlock(&itransfer->lock); - if (r != LIBUSB_SUCCESS) + if (r != LIBUSB_SUCCESS) { + usbi_mutex_lock(&ctx->flying_transfers_lock); remove_from_flying_list(itransfer); + usbi_mutex_unlock(&ctx->flying_transfers_lock); + } return r; } @@ -1695,7 +1690,9 @@ int usbi_handle_transfer_completion(struct usbi_transfer *itransfer, uint8_t flags; int r; + usbi_mutex_lock(&ctx->flying_transfers_lock); r = remove_from_flying_list(itransfer); + usbi_mutex_unlock(&ctx->flying_transfers_lock); if (r < 0) usbi_err(ctx, "failed to set timer for next timeout"); @@ -2047,6 +2044,7 @@ int API_EXPORTED libusb_wait_for_event(libusb_context *ctx, struct timeval *tv) return 0; } +// NB: flying_transfers_lock must be held when calling this static void handle_timeout(struct usbi_transfer *itransfer) { struct libusb_transfer *transfer = @@ -2062,6 +2060,7 @@ static void handle_timeout(struct usbi_transfer *itransfer) "async cancel failed %d", r); } +// NB: flying_transfers_lock must be held when calling this static void handle_timeouts_locked(struct libusb_context *ctx) { struct timespec systime; @@ -2629,11 +2628,11 @@ int API_EXPORTED libusb_get_next_timeout(libusb_context *ctx, * To remove notifiers, pass NULL values for the function pointers. * * Note that file descriptors may have been added even before you register - * these notifiers (e.g. at libusb_init() time). + * these notifiers (e.g. at libusb_init_context() time). * * Additionally, note that the removal notifier may be called during * libusb_exit() (e.g. when it is closing file descriptors that were opened - * and added to the poll set at libusb_init() time). If you don't want this, + * and added to the poll set at libusb_init_context() time). If you don't want this, * remove the notifiers immediately before calling libusb_exit(). * * \param ctx the context to operate on, or NULL for the default context @@ -2837,7 +2836,8 @@ void usbi_handle_disconnect(struct libusb_device_handle *dev_handle) to_cancel = NULL; usbi_mutex_lock(&ctx->flying_transfers_lock); for_each_transfer(ctx, cur) { - if (USBI_TRANSFER_TO_LIBUSB_TRANSFER(cur)->dev_handle == dev_handle) { + struct libusb_transfer *cur_transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(cur); + if (cur_transfer->dev_handle == dev_handle) { usbi_mutex_lock(&cur->lock); if (cur->state_flags & USBI_TRANSFER_IN_FLIGHT) to_cancel = cur; @@ -2852,8 +2852,9 @@ void usbi_handle_disconnect(struct libusb_device_handle *dev_handle) if (!to_cancel) break; + struct libusb_transfer *transfer_to_cancel = USBI_TRANSFER_TO_LIBUSB_TRANSFER(to_cancel); usbi_dbg(ctx, "cancelling transfer %p from disconnect", - (void *) USBI_TRANSFER_TO_LIBUSB_TRANSFER(to_cancel)); + (void *) transfer_to_cancel); usbi_mutex_lock(&to_cancel->lock); usbi_backend.clear_transfer_priv(to_cancel); diff --git a/libusb/libusb/libusb.h b/libusb/libusb/libusb.h index 99cc017..f4e9203 100644 --- a/libusb/libusb/libusb.h +++ b/libusb/libusb/libusb.h @@ -5,7 +5,7 @@ * Copyright © 2012 Pete Batard * Copyright © 2012-2023 Nathan Hjelm * Copyright © 2014-2020 Chris Dickens - * For more information, please visit: http://libusb.info + * For more information, please visit: https://libusb.info * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -50,9 +50,9 @@ typedef SSIZE_T ssize_t; #include #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) -#define ZERO_SIZED_ARRAY /* [] - valid C99 code */ +#define LIBUSB_FLEXIBLE_ARRAY /* [] - valid C99 code */ #else -#define ZERO_SIZED_ARRAY 0 /* [0] - non-standard, but usually working code */ +#define LIBUSB_FLEXIBLE_ARRAY 0 /* [0] - non-standard, but usually working code */ #endif /* __STDC_VERSION__ */ /* 'interface' might be defined as a macro on Windows, so we need to @@ -130,12 +130,15 @@ typedef SSIZE_T ssize_t; * \ingroup libusb_misc * libusb's API version. * - * Since version 1.0.13, to help with feature detection, libusb defines + * Since version 1.0.18, to help with feature detection, libusb defines * a LIBUSB_API_VERSION macro that gets increased every time there is a * significant change to the API, such as the introduction of a new call, * the definition of a new macro/enum member, or any other element that * libusb applications may want to detect at compilation time. * + * Between versions 1.0.13 and 1.0.17 (inclusive) the older spelling of + * LIBUSBX_API_VERSION was used. + * * The macro is typically used in an application as follows: * \code * #if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01001234) @@ -145,10 +148,34 @@ typedef SSIZE_T ssize_t; * * Internally, LIBUSB_API_VERSION is defined as follows: * (libusb major << 24) | (libusb minor << 16) | (16 bit incremental) + * + * The incremental component has changed as follows: + *
    + *
  • libusbx version 1.0.13: LIBUSBX_API_VERSION = 0x01000100 + *
  • libusbx version 1.0.14: LIBUSBX_API_VERSION = 0x010000FF + *
  • libusbx version 1.0.15: LIBUSBX_API_VERSION = 0x01000101 + *
  • libusbx version 1.0.16: LIBUSBX_API_VERSION = 0x01000102 + *
  • libusbx version 1.0.17: LIBUSBX_API_VERSION = 0x01000102 + *
  • libusb version 1.0.18: LIBUSB_API_VERSION = 0x01000102 + *
  • libusb version 1.0.19: LIBUSB_API_VERSION = 0x01000103 + *
  • libusb version 1.0.20: LIBUSB_API_VERSION = 0x01000104 + *
  • libusb version 1.0.21: LIBUSB_API_VERSION = 0x01000105 + *
  • libusb version 1.0.22: LIBUSB_API_VERSION = 0x01000106 + *
  • libusb version 1.0.23: LIBUSB_API_VERSION = 0x01000107 + *
  • libusb version 1.0.24: LIBUSB_API_VERSION = 0x01000108 + *
  • libusb version 1.0.25: LIBUSB_API_VERSION = 0x01000109 + *
  • libusb version 1.0.26: LIBUSB_API_VERSION = 0x01000109 + *
  • libusb version 1.0.27: LIBUSB_API_VERSION = 0x0100010A + *
*/ #define LIBUSB_API_VERSION 0x0100010A -/* The following is kept for compatibility, but will be deprecated in the future */ +/** \def LIBUSBX_API_VERSION + * \ingroup libusb_misc + * + * This is the older spelling, kept for backwards compatibility of code + * needing to test for older library versions where the newer spelling + * did not exist. */ #define LIBUSBX_API_VERSION LIBUSB_API_VERSION #if defined(__cplusplus) @@ -857,7 +884,7 @@ struct libusb_bos_dev_capability_descriptor { uint8_t bDevCapabilityType; /** Device Capability data (bLength - 3 bytes) */ - uint8_t dev_capability_data[ZERO_SIZED_ARRAY]; + uint8_t dev_capability_data[LIBUSB_FLEXIBLE_ARRAY]; }; /** \ingroup libusb_desc @@ -882,7 +909,7 @@ struct libusb_bos_descriptor { uint8_t bNumDeviceCaps; /** bNumDeviceCap Device Capability Descriptors */ - struct libusb_bos_dev_capability_descriptor *dev_capability[ZERO_SIZED_ARRAY]; + struct libusb_bos_dev_capability_descriptor *dev_capability[LIBUSB_FLEXIBLE_ARRAY]; }; /** \ingroup libusb_desc @@ -1004,7 +1031,7 @@ struct libusb_platform_descriptor { uint8_t PlatformCapabilityUUID[16]; /** Capability data (bLength - 20) */ - uint8_t CapabilityData[ZERO_SIZED_ARRAY]; + uint8_t CapabilityData[LIBUSB_FLEXIBLE_ARRAY]; }; /** \ingroup libusb_asyncio @@ -1081,7 +1108,7 @@ struct libusb_version { * libusb_exit() will not destroy resources that the other user is still * using. * - * Sessions are created by libusb_init() and destroyed through libusb_exit(). + * Sessions are created by libusb_init_context() and destroyed through libusb_exit(). * If your application is guaranteed to only ever include a single libusb * user (i.e. you), you do not have to worry about contexts: pass NULL in * every function call where a context is required, and the default context @@ -1384,7 +1411,7 @@ struct libusb_transfer { int num_iso_packets; /** Isochronous packet descriptors, for isochronous transfers only. */ - struct libusb_iso_packet_descriptor iso_packet_desc[ZERO_SIZED_ARRAY]; + struct libusb_iso_packet_descriptor iso_packet_desc[LIBUSB_FLEXIBLE_ARRAY]; }; /** \ingroup libusb_misc @@ -1451,6 +1478,7 @@ enum libusb_log_cb_mode { enum libusb_option { /** Set the log message verbosity. * + * This option must be provided an argument of type \ref libusb_log_level. * The default level is LIBUSB_LOG_LEVEL_NONE, which means no messages are ever * printed. If you choose to increase the message verbosity level, ensure * that your application does not close the stderr file descriptor. @@ -1461,22 +1489,21 @@ enum libusb_option { * your software. * * If the LIBUSB_DEBUG environment variable was set when libusb was - * initialized, this function does nothing: the message verbosity is fixed + * initialized, this option does nothing: the message verbosity is fixed * to the value in the environment variable. * - * If libusb was compiled without any message logging, this function does + * If libusb was compiled without any message logging, this option does * nothing: you'll never get any messages. * - * If libusb was compiled with verbose debug message logging, this function + * If libusb was compiled with verbose debug message logging, this option * does nothing: you'll always get messages from all levels. */ LIBUSB_OPTION_LOG_LEVEL = 0, /** Use the UsbDk backend for a specific context, if available. * - * This option should be set immediately after calling libusb_init(), or set at - * initialization with libusb_init_context() otherwise unspecified behavior - * may occur. + * This option should be set at initialization with libusb_init_context() + * otherwise unspecified behavior may occur. * * Only valid on Windows. Ignored on all other platforms. */ @@ -1495,57 +1522,27 @@ enum libusb_option { * This is typically needed on Android, where access to USB devices * is limited. * + * This option should only be used with libusb_init_context() + * otherwise unspecified behavior may occur. + * * Only valid on Linux. Ignored on all other platforms. */ LIBUSB_OPTION_NO_DEVICE_DISCOVERY = 2, #define LIBUSB_OPTION_WEAK_AUTHORITY LIBUSB_OPTION_NO_DEVICE_DISCOVERY - /** Enable or disable WinUSB RAW_IO mode on an endpoint - * - * Requires four additional arguments of: - * - * libusb_device_handle *dev_handle - * unsigned int endpoint_address - * unsigned int enable - * unsigned int *max_transfer_size_ptr - * - * The dev_handle and endpoint_address parameters must identify a valid - * IN endpoint on an open device. If enable is nonzero, RAW_IO is - * enabled, otherwise it is disabled. Unless max_transfer_size_ptr is - * NULL, then on a successful call to enable RAW_IO, it will be written - * with the MAXIMUM_TRANSFER_SIZE value for the endpoint. - * - * Whilst RAW_IO is enabled for an endpoint, all transfers on that endpoint - * must meet the following two requirements: - * - * * The buffer length must be a multiple of the maximum endpoint packet size. - * - * * The length must be less than or equal to the MAXIMUM_TRANSFER_SIZE value. - * - * This option should not be changed when any transfer is in progress on the - * specified endpoint. - * - * This option only affects the WinUSB backend. On other backends it is ignored - * and returns LIBUSB_OPTION_NOT_SUPPORTED, without modifying the value pointed - * to by max_transfer_size_ptr. - * - * Since version 1.0.27, \ref LIBUSB_API_VERSION >= 0x0100010A - */ - LIBUSB_OPTION_WINUSB_RAW_IO = 3, - - /** Set the context log callback functon. + /** Set the context log callback function. * * Set the log callback function either on a context or globally. This - * option must be provided an argument of type libusb_log_cb. Using this - * option with a NULL context is equivalent to calling libusb_set_log_cb - * with mode LIBUSB_LOG_CB_GLOBAL. Using it with a non-NULL context is - * equivalent to calling libusb_set_log_cb with mode - * LIBUSB_LOG_CB_CONTEXT. + * option must be provided an argument of type \ref libusb_log_cb. + * Using this option with a NULL context is equivalent to calling + * libusb_set_log_cb() with mode \ref LIBUSB_LOG_CB_GLOBAL. + * Using it with a non-NULL context is equivalent to calling + * libusb_set_log_cb() with mode \ref LIBUSB_LOG_CB_CONTEXT. */ - LIBUSB_OPTION_LOG_CB = 4, + LIBUSB_OPTION_LOG_CB = 3, - LIBUSB_OPTION_MAX = 5 + LIBUSB_OPTION_MAX = 4 }; /** \ingroup libusb_lib @@ -1571,7 +1568,7 @@ struct libusb_init_option { enum libusb_option option; /** An integer value used by the option (if applicable). */ union { - int64_t ival; + int ival; libusb_log_cb log_cbval; } value; }; diff --git a/libusb/libusb/libusbi.h b/libusb/libusb/libusbi.h index 030e6b6..3b0c610 100644 --- a/libusb/libusb/libusbi.h +++ b/libusb/libusb/libusbi.h @@ -73,7 +73,7 @@ #endif /* The following is used to silence warnings for unused variables */ -#if defined(UNREFERENCED_PARAMETER) +#if defined(UNREFERENCED_PARAMETER) && !defined(__GNUC__) #define UNUSED(var) UNREFERENCED_PARAMETER(var) #else #define UNUSED(var) do { (void)(var); } while(0) @@ -380,7 +380,7 @@ struct libusb_context { struct list_head flying_transfers; /* Note paths taking both this and usbi_transfer->lock must always * take this lock first */ - usbi_mutex_t flying_transfers_lock; + usbi_mutex_t flying_transfers_lock; /* for flying_transfers and timeout_flags */ #if !defined(PLATFORM_WINDOWS) /* user callbacks for pollfd changes */ @@ -563,8 +563,11 @@ void usbi_get_real_time(struct timespec *tp); * 2. struct usbi_transfer * 3. struct libusb_transfer (which includes iso packets) [variable size] * - * from a libusb_transfer, you can get the usbi_transfer by rewinding the - * appropriate number of bytes. + * You can convert between them with the macros: + * TRANSFER_PRIV_TO_USBI_TRANSFER + * USBI_TRANSFER_TO_TRANSFER_PRIV + * USBI_TRANSFER_TO_LIBUSB_TRANSFER + * LIBUSB_TRANSFER_TO_USBI_TRANSFER */ struct usbi_transfer { @@ -575,7 +578,7 @@ struct usbi_transfer { int transferred; uint32_t stream_id; uint32_t state_flags; /* Protected by usbi_transfer->lock */ - uint32_t timeout_flags; /* Protected by the flying_stransfers_lock */ + uint32_t timeout_flags; /* Protected by the flying_transfers_lock */ /* The device reference is held until destruction for logging * even after dev_handle is set to NULL. */ @@ -617,10 +620,21 @@ enum usbi_transfer_timeout_flags { USBI_TRANSFER_TIMED_OUT = 1U << 2, }; +#define TRANSFER_PRIV_TO_USBI_TRANSFER(transfer_priv) \ + ((struct usbi_transfer *) \ + ((unsigned char *)(transfer_priv) \ + + PTR_ALIGN(sizeof(*transfer_priv)))) + +#define USBI_TRANSFER_TO_TRANSFER_PRIV(itransfer) \ + ((unsigned char *) \ + ((unsigned char *)(itransfer) \ + - PTR_ALIGN(usbi_backend.transfer_priv_size))) + #define USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer) \ ((struct libusb_transfer *) \ ((unsigned char *)(itransfer) \ + PTR_ALIGN(sizeof(struct usbi_transfer)))) + #define LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer) \ ((struct usbi_transfer *) \ ((unsigned char *)(transfer) \ @@ -679,7 +693,7 @@ struct usbi_interface_descriptor { struct usbi_string_descriptor { uint8_t bLength; uint8_t bDescriptorType; - uint16_t wData[ZERO_SIZED_ARRAY]; + uint16_t wData[LIBUSB_FLEXIBLE_ARRAY]; } LIBUSB_PACKED; struct usbi_bos_descriptor { @@ -893,7 +907,7 @@ static inline void *usbi_get_transfer_priv(struct usbi_transfer *itransfer) struct discovered_devs { size_t len; size_t capacity; - struct libusb_device *devices[ZERO_SIZED_ARRAY]; + struct libusb_device *devices[LIBUSB_FLEXIBLE_ARRAY]; }; struct discovered_devs *discovered_devs_append( @@ -1182,6 +1196,8 @@ struct usbi_os_backend { * claiming, no other drivers/applications can use the interface because * we now "own" it. * + * This function gets called with dev_handle->lock locked! + * * Return: * - 0 on success * - LIBUSB_ERROR_NOT_FOUND if the interface does not exist @@ -1201,6 +1217,8 @@ struct usbi_os_backend { * You will only ever be asked to release an interface which was * successfully claimed earlier. * + * This function gets called with dev_handle->lock locked! + * * Return: * - 0 on success * - LIBUSB_ERROR_NO_DEVICE if the device has been disconnected since it @@ -1337,7 +1355,7 @@ struct usbi_os_backend { * * This function must not block. * - * This function gets called with the flying_transfers_lock locked! + * This function gets called with itransfer->lock locked! * * Return: * - 0 on success @@ -1351,6 +1369,8 @@ struct usbi_os_backend { * This function must not block. The transfer cancellation must complete * later, resulting in a call to usbi_handle_transfer_cancellation() * from the context of handle_events. + * + * This function gets called with itransfer->lock locked! */ int (*cancel_transfer)(struct usbi_transfer *itransfer); diff --git a/libusb/libusb/os/darwin_usb.c b/libusb/libusb/os/darwin_usb.c index 574b7bb..c0963e0 100644 --- a/libusb/libusb/os/darwin_usb.c +++ b/libusb/libusb/os/darwin_usb.c @@ -1,8 +1,8 @@ /* -*- Mode: C; indent-tabs-mode:nil -*- */ /* * darwin backend for libusb 1.0 - * Copyright © 2008-2021 Nathan Hjelm - * Copyright © 2019-2022 Google LLC. All rights reserved. + * Copyright © 2008-2023 Nathan Hjelm + * Copyright © 2019-2023 Google LLC. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -69,11 +69,19 @@ static usbi_mutex_t darwin_cached_devices_mutex = PTHREAD_MUTEX_INITIALIZER; static struct list_head darwin_cached_devices; static const char *darwin_device_class = "IOUSBDevice"; +uint32_t libusb_testonly_fake_running_version __attribute__ ((visibility ("hidden"))); +int libusb_testonly_using_running_interface_version __attribute__ ((visibility ("hidden"))); +int libusb_testonly_using_running_device_version __attribute__ ((visibility ("hidden"))); +bool libusb_testonly_clear_running_version_cache __attribute__ ((visibility ("hidden"))); + #define DARWIN_CACHED_DEVICE(a) (((struct darwin_device_priv *)usbi_get_device_priv((a)))->dev) /* async event thread */ static pthread_t libusb_darwin_at; +/* protected by libusb_darwin_at_mutex */ +static bool libusb_darwin_at_started; + static void darwin_exit(struct libusb_context *ctx); static int darwin_get_config_descriptor(struct libusb_device *dev, uint8_t config_index, void *buffer, size_t len); static int darwin_claim_interface(struct libusb_device_handle *dev_handle, uint8_t iface); @@ -91,6 +99,186 @@ static enum libusb_error process_new_device (struct libusb_context *ctx, struct static enum libusb_error darwin_get_cached_device(struct libusb_context *ctx, io_service_t service, struct darwin_cached_device **cached_out, UInt64 *old_session_id); +struct darwin_iokit_interface { + uint32_t min_os_version; + uint32_t version; + CFUUIDRef interface_id; +}; + +static const struct darwin_iokit_interface *get_interface_interface(void) { + const struct darwin_iokit_interface interfaces[] = { +#if defined(kIOUSBInterfaceInterfaceID800) + { + .min_os_version = 101200, + .version = 800, + .interface_id = kIOUSBInterfaceInterfaceID800, + }, +#endif +#if defined(kIOUSBInterfaceInterfaceID700) + { + .min_os_version = 101000, + .version = 700, + .interface_id = kIOUSBInterfaceInterfaceID700, + }, +#endif +#if defined(kIOUSBInterfaceInterfaceID650) + { + .min_os_version = 100900, + .version = 650, + .interface_id = kIOUSBInterfaceInterfaceID650 + }, +#endif +#if defined(kIOUSBInterfaceInterfaceID550) + { + .min_os_version = 100803, + .version = 550, + .interface_id = kIOUSBInterfaceInterfaceID550, + }, +#endif +#if defined(kIOUSBInterfaceInterfaceID245) + { + .min_os_version = 100407, + .version = 245, + .interface_id = kIOUSBInterfaceInterfaceID245, + }, +#endif + { + .min_os_version = 100000, + .version = 220, + .interface_id = kIOUSBInterfaceInterfaceID220, + }, + { + .version = 0, + }, + }; + static struct darwin_iokit_interface cached_interface = {.version = 0}; + if (libusb_testonly_clear_running_version_cache) { + memset (&cached_interface, 0, sizeof (cached_interface)); + } + if (0 == cached_interface.version) { + uint32_t os_version = get_running_version(); + for (int i = 0 ; interfaces[i].version > 0 ; ++i) { + if (os_version >= interfaces[i].min_os_version && cached_interface.min_os_version < interfaces[i].min_os_version) { + cached_interface = interfaces[i]; + } + } + + libusb_testonly_using_running_interface_version = cached_interface.version; + } + + return &cached_interface; +} + +static CFUUIDRef get_interface_interface_id(void) { + return get_interface_interface()->interface_id; +} + +static int get_interface_interface_version(void) { + return get_interface_interface()->version; +} + +static const struct darwin_iokit_interface *get_device_interface(void) { + struct darwin_iokit_interface interfaces[] = { +#if defined(kIOUSBDeviceInterfaceID650) + { + .min_os_version = 100900, + .version = 650, + .interface_id = kIOUSBDeviceInterfaceID650, + }, +#endif +#if defined(kIOUSBDeviceInterfaceID500) + { + .min_os_version = 100703, + .version = 500, + .interface_id = kIOUSBDeviceInterfaceID500, + }, +#endif +#if defined(kIOUSBDeviceInterfaceID320) + { + .min_os_version = 100504, + .version = 320, + .interface_id = kIOUSBDeviceInterfaceID320, + }, +#endif +#if defined(kIOUSBDeviceInterfaceID300) + { + .min_os_version = 100500, + .version = 300, + .interface_id = kIOUSBDeviceInterfaceID300, + }, +#endif +#if defined(kIOUSBDeviceInterfaceID245) + { + .min_os_version = 100407, + .version = 245, + .interface_id = kIOUSBDeviceInterfaceID245, + }, +#endif + { + .min_os_version = 100000, + .version = 197, + .interface_id = kIOUSBDeviceInterfaceID197, + }, + { + .version = 0, + }, + }; + static struct darwin_iokit_interface cached_interface = {.version = 0}; + if (libusb_testonly_clear_running_version_cache) { + memset (&cached_interface, 0, sizeof (cached_interface)); + } + if (0 == cached_interface.version) { + uint32_t os_version = get_running_version(); + for (int i = 0 ; interfaces[i].version > 0 ; ++i) { + if (os_version >= interfaces[i].min_os_version && cached_interface.min_os_version < interfaces[i].min_os_version) { + cached_interface = interfaces[i]; + } + } + libusb_testonly_using_running_device_version = cached_interface.version; + } + + return &cached_interface; +} + +static CFUUIDRef get_device_interface_id(void) { + return get_device_interface()->interface_id; +} + +static int get_device_interface_version(void) { + return get_device_interface()->version; +} + +struct darwin_pipe_properties { + uint8_t number; + uint8_t direction; + uint8_t transfer_type; + uint16_t max_packet_size; + uint8_t interval; +}; +typedef struct darwin_pipe_properties darwin_pipe_properties_t; + +static IOReturn darwin_get_pipe_properties(struct darwin_interface *cInterface, uint8_t pipe, darwin_pipe_properties_t *out) { + IOReturn kresult; + +#if (MAX_INTERFACE_VERSION >= 550) + if (get_interface_interface_version() >= 550) { + IOUSBEndpointProperties pipe_properties = {.bVersion = kUSBEndpointPropertiesVersion3}; + kresult = (*IOINTERFACE_V(cInterface, 550))->GetPipePropertiesV3 (IOINTERFACE(cInterface), pipe, &pipe_properties); + if (kIOReturnSuccess == kresult) { + out->number = pipe_properties.bEndpointNumber; + out->direction = pipe_properties.bDirection; + out->transfer_type = pipe_properties.bTransferType; + out->max_packet_size = pipe_properties.wMaxPacketSize; + out->interval = pipe_properties.bInterval; + } + return kresult; + } +#endif + return (*IOINTERFACE(cInterface))->GetPipeProperties(IOINTERFACE(cInterface), pipe, &out->direction, + &out->number, &out->transfer_type, &out->max_packet_size, + &out->interval); +} + #if defined(ENABLE_LOGGING) static const char *darwin_error_str (IOReturn result) { static char string_buffer[50]; @@ -167,6 +355,71 @@ static enum libusb_error darwin_to_libusb (IOReturn result) { } } +uint32_t get_running_version(void) { + if (libusb_testonly_fake_running_version > 0) { + return libusb_testonly_fake_running_version; + } + + int ret; +#if !defined(TARGET_OS_OSX) || TARGET_OS_OSX == 1 + char os_version_string[64] = {'\0'};; + size_t os_version_string_len = sizeof(os_version_string) - 1; + + /* newer versions of macOS provide a sysctl for the OS version but this is not useful for iOS without + * code detecting this is iOS and a mapping from iOS -> macOS version. it is still useful to have since + * it provides the exact macOS version instead of the approximate version (as below). */ + ret = sysctlbyname("kern.osproductversion", os_version_string, &os_version_string_len, NULL, 0); + if (ret == 0) { + int major = 10, minor = 0, patch = 0; + ret = sscanf(os_version_string, "%i.%i.%i", &major, &minor, &patch); + if (ret < 2) { + usbi_err (NULL, "could not determine the running OS version, assuming 10.0, kern.osproductversion=%s", os_version_string); + return 100000; + } + return (major * 10000) + (minor * 100) + patch; + } +#endif + + char os_release_string[64] = {'\0'}; + size_t os_release_string_len = sizeof(os_release_string) - 1; + /* if the version can not be detected libusb assumes 10.0 so ignore any error here */ + ret = sysctlbyname("kern.osrelease", os_release_string, &os_release_string_len, NULL, 0); + if (ret != 0) { + usbi_err (NULL, "could not read kern.osrelease, errno=", errno); + return 100000; + } + + int darwin_major = 1, darwin_minor = 0; + ret = sscanf(os_release_string, "%i.%i", &darwin_major, &darwin_minor); + if (ret < 1) { + usbi_err (NULL, "could not determine the running Darwin version, assuming 1.3 (OS X 10.0), kern.osrelease=%s", os_release_string); + return 100000; + } + + int major = 10, minor = 0, patch = 0; + + if (1 == darwin_major && darwin_minor < 4) { + /* 10.0.x */ + } else if (darwin_major < 6) { + /* assume 10.1 for anything in this range */ + minor = 1; + } else if (darwin_major < 20) { + /* from macOS 10.2 through 10.15 the minor version can be calculated from the darwin_major by subtracting 4 and + * the patch level almost always matches darwin_minor. when the darwin_minor does not match the OS X patch level + * it is usually because Apple did not change it in a particular point release. when darwin_minor is changed it + * always matches the OS X/macOS patch level. */ + minor = darwin_major - 4; + patch = darwin_minor; + } else { + /* unlikely to be used as kern.osproductversion is available from 10.10 on */ + major = darwin_major - 9; + minor = darwin_minor; + /* ignore the patch level in this range */ + } + + return (major * 10000) + (minor * 100) + patch; +} + /* this function must be called with the darwin_cached_devices_mutex held */ static void darwin_deref_cached_device(struct darwin_cached_device *cached_dev) { cached_dev->refcount--; @@ -175,7 +428,7 @@ static void darwin_deref_cached_device(struct darwin_cached_device *cached_dev) list_del(&cached_dev->list); if (cached_dev->device) { - (*(cached_dev->device))->Release(cached_dev->device); + (*cached_dev->device)->Release(cached_dev->device); cached_dev->device = NULL; } IOObjectRelease (cached_dev->service); @@ -295,12 +548,12 @@ static bool get_ioregistry_value_data (io_service_t service, CFStringRef propert return success; } -static usb_device_t **darwin_device_from_service (struct libusb_context *ctx, io_service_t service) +static int darwin_device_from_service (struct libusb_context *ctx, io_service_t service, usb_device_t* device) { io_cf_plugin_ref_t *plugInInterface = NULL; - usb_device_t **device; IOReturn kresult; SInt32 score; + const int max_retries = 5; /* The IOCreatePlugInInterfaceForService function might consistently return @@ -320,17 +573,21 @@ static usb_device_t **darwin_device_from_service (struct libusb_context *ctx, io nanosleep(&(struct timespec){.tv_sec = 0, .tv_nsec = 1000}, NULL); } - if (kIOReturnSuccess != kresult || !plugInInterface) { + if (kIOReturnSuccess != kresult) { usbi_dbg (ctx, "could not set up plugin for service: %s", darwin_error_str (kresult)); - return NULL; + return darwin_to_libusb(kresult); + } + if (!plugInInterface) { + usbi_dbg (ctx, "could not set up plugin for service"); + return LIBUSB_ERROR_OTHER; } - (void)(*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(DeviceInterfaceID), - (LPVOID)&device); + (void)(*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(get_device_interface_id()), + (LPVOID)device); /* Use release instead of IODestroyPlugInInterface to avoid stopping IOServices associated with this device */ (*plugInInterface)->Release (plugInInterface); - return device; + return LIBUSB_SUCCESS; } static void darwin_devices_attached (void *ptr, io_iterator_t add_devices) { @@ -372,8 +629,6 @@ static void darwin_devices_detached (void *ptr, io_iterator_t rem_devices) { struct darwin_cached_device *old_device; io_service_t device; - UInt64 session, locationID; - int ret; usbi_mutex_lock(&active_contexts_lock); @@ -381,7 +636,9 @@ static void darwin_devices_detached (void *ptr, io_iterator_t rem_devices) { bool is_reenumerating = false; /* get the location from the i/o registry */ - ret = get_ioregistry_value_number (device, CFSTR("sessionID"), kCFNumberSInt64Type, &session); + UInt64 session = 0; + bool ret = get_ioregistry_value_number (device, CFSTR("sessionID"), kCFNumberSInt64Type, &session); + UInt32 locationID = 0; (void) get_ioregistry_value_number (device, CFSTR("locationID"), kCFNumberSInt32Type, &locationID); IOObjectRelease (device); if (!ret) @@ -395,12 +652,12 @@ static void darwin_devices_detached (void *ptr, io_iterator_t rem_devices) { if (old_device->in_reenumerate) { /* device is re-enumerating. do not dereference the device at this time. libusb_reset_device() * will deref if needed. */ - usbi_dbg (NULL, "detected device detached due to re-enumeration. sessionID: 0x%" PRIx64 ", locationID: 0x%" PRIx64, - session, locationID); + usbi_dbg (NULL, "detected device detached due to re-enumeration. sessionID: 0x%" PRIx64 + ", locationID: 0x%" PRIx32, session, locationID); /* the device object is no longer usable so go ahead and release it */ if (old_device->device) { - (*(old_device->device))->Release(old_device->device); + (*old_device->device)->Release(old_device->device); old_device->device = NULL; } @@ -595,6 +852,16 @@ static int darwin_first_time_init(void) { list_init (&darwin_cached_devices); } + /* cache the interface versions that will be used. as a sanity check verify + * that the interface versions are non-zero. */ + const struct darwin_iokit_interface *interface_interface = get_interface_interface(); + const struct darwin_iokit_interface *device_interface = get_device_interface(); + if (0 == interface_interface->version || 0 == device_interface->version) { + usbi_err(NULL, "could not determine the device or interface interface to use with this version " + "of macOS (or MacOS X), current_running_version = %" PRIu32, get_running_version()); + return LIBUSB_ERROR_OTHER; + } + if (!list_empty(&darwin_cached_devices)) { usbi_err(NULL, "libusb_device reference not released on last exit. will not continue"); return LIBUSB_ERROR_OTHER; @@ -607,6 +874,7 @@ static int darwin_first_time_init(void) { } pthread_mutex_lock (&libusb_darwin_at_mutex); + libusb_darwin_at_started = true; while (NULL == libusb_darwin_acfl) { pthread_cond_wait (&libusb_darwin_at_cond, &libusb_darwin_at_mutex); } @@ -654,12 +922,18 @@ static void darwin_exit (struct libusb_context *ctx) { if (0 == --init_count) { /* stop the event runloop and wait for the thread to terminate. */ pthread_mutex_lock (&libusb_darwin_at_mutex); - CFRunLoopSourceSignal (libusb_darwin_acfls); - CFRunLoopWakeUp (libusb_darwin_acfl); - while (libusb_darwin_acfl) - pthread_cond_wait (&libusb_darwin_at_cond, &libusb_darwin_at_mutex); + if (NULL != libusb_darwin_acfls) { + CFRunLoopSourceSignal (libusb_darwin_acfls); + CFRunLoopWakeUp (libusb_darwin_acfl); + while (libusb_darwin_acfl) + pthread_cond_wait (&libusb_darwin_at_cond, &libusb_darwin_at_mutex); + } + + if (libusb_darwin_at_started) { + pthread_join (libusb_darwin_at, NULL); + libusb_darwin_at_started = false; + } pthread_mutex_unlock (&libusb_darwin_at_mutex); - pthread_join (libusb_darwin_at, NULL); darwin_cleanup_devices (); } @@ -673,12 +947,12 @@ static int get_configuration_index (struct libusb_device *dev, UInt8 config_valu IOReturn kresult; /* is there a simpler way to determine the index? */ - kresult = (*(priv->device))->GetNumberOfConfigurations (priv->device, &numConfig); + kresult = (*priv->device)->GetNumberOfConfigurations (priv->device, &numConfig); if (kresult != kIOReturnSuccess) return darwin_to_libusb (kresult); for (i = 0 ; i < numConfig ; i++) { - (*(priv->device))->GetConfigurationDescriptorPtr (priv->device, i, &desc); + (*priv->device)->GetConfigurationDescriptorPtr (priv->device, i, &desc); if (desc->bConfigurationValue == config_value) return i; @@ -730,7 +1004,7 @@ static int darwin_get_config_descriptor(struct libusb_device *dev, uint8_t confi /* check whether the os has configured the device */ static enum libusb_error darwin_check_configuration (struct libusb_context *ctx, struct darwin_cached_device *dev) { - usb_device_t **darwin_device = dev->device; + usb_device_t darwin_device = dev->device; IOUSBConfigurationDescriptorPtr configDesc; IOUSBFindInterfaceRequest request; @@ -766,7 +1040,7 @@ static enum libusb_error darwin_check_configuration (struct libusb_context *ctx, request.bInterfaceProtocol = kIOUSBFindInterfaceDontCare; request.bAlternateSetting = kIOUSBFindInterfaceDontCare; - kresult = (*(darwin_device))->CreateInterfaceIterator(darwin_device, &request, &interface_iterator); + kresult = (*darwin_device)->CreateInterfaceIterator(darwin_device, &request, &interface_iterator); if (kresult != kIOReturnSuccess) return darwin_to_libusb (kresult); @@ -795,7 +1069,7 @@ static enum libusb_error darwin_check_configuration (struct libusb_context *ctx, return LIBUSB_SUCCESS; } -static IOReturn darwin_request_descriptor (usb_device_t **device, UInt8 desc, UInt8 desc_index, void *buffer, size_t buffer_size) { +static IOReturn darwin_request_descriptor (usb_device_t device, UInt8 desc, UInt8 desc_index, void *buffer, size_t buffer_size) { IOUSBDevRequestTO req; assert(buffer_size <= UINT16_MAX); @@ -816,7 +1090,7 @@ static IOReturn darwin_request_descriptor (usb_device_t **device, UInt8 desc, UI } static enum libusb_error darwin_cache_device_descriptor (struct libusb_context *ctx, struct darwin_cached_device *dev) { - usb_device_t **device = dev->device; + usb_device_t device = dev->device; int retries = 1; long delay = 30000; // microseconds int unsuspended = 0, try_unsuspend = 1, try_reconfigure = 1; @@ -867,15 +1141,17 @@ static enum libusb_error darwin_cache_device_descriptor (struct libusb_context * if (kIOReturnSuccess != ret && is_open && try_unsuspend) { /* device may be suspended. unsuspend it and try again */ -#if DeviceVersion >= 320 - UInt32 info = 0; +#if MAX_DEVICE_VERSION >= 320 + if (get_device_interface_version() >= 320) { + UInt32 info = 0; - /* IOUSBFamily 320+ provides a way to detect device suspension but earlier versions do not */ - (void)(*device)->GetUSBDeviceInformation (device, &info); + /* IOUSBFamily 320+ provides a way to detect device suspension but earlier versions do not */ + (void)(*IODEVICE_V(device, 320))->GetUSBDeviceInformation (device, &info); - /* note that the device was suspended */ - if (info & (1U << kUSBInformationDeviceIsSuspendedBit) || 0 == info) - try_unsuspend = 1; + /* note that the device was suspended */ + if (info & (1U << kUSBInformationDeviceIsSuspendedBit) || 0 == info) + try_unsuspend = 1; + } #endif if (try_unsuspend) { @@ -988,7 +1264,7 @@ static enum libusb_error darwin_get_cached_device(struct libusb_context *ctx, io UInt64 sessionID = 0, parent_sessionID = 0; UInt32 locationID = 0; enum libusb_error ret = LIBUSB_SUCCESS; - usb_device_t **device; + usb_device_t device; UInt8 port = 0; /* assuming sessionID != 0 normally (never seen it be 0) */ @@ -1011,7 +1287,7 @@ static enum libusb_error darwin_get_cached_device(struct libusb_context *ctx, io usbi_mutex_lock(&darwin_cached_devices_mutex); do { list_for_each_entry(new_device, &darwin_cached_devices, list, struct darwin_cached_device) { - usbi_dbg(ctx, "matching sessionID/locationID 0x%" PRIx64 "/0x%x against cached device with sessionID/locationID 0x%" PRIx64 "/0x%x", + usbi_dbg(ctx, "matching sessionID/locationID 0x%" PRIx64 "/0x%" PRIx32 " against cached device with sessionID/locationID 0x%" PRIx64 "/0x%" PRIx32, sessionID, locationID, new_device->session, new_device->location); if (new_device->location == locationID && new_device->in_reenumerate) { usbi_dbg (ctx, "found cached device with matching location that is being re-enumerated"); @@ -1031,9 +1307,8 @@ static enum libusb_error darwin_get_cached_device(struct libusb_context *ctx, io usbi_dbg(ctx, "caching new device with sessionID 0x%" PRIx64, sessionID); - device = darwin_device_from_service (ctx, service); - if (!device) { - ret = LIBUSB_ERROR_NO_DEVICE; + ret = darwin_device_from_service (ctx, service, &device); + if (LIBUSB_SUCCESS != ret) { break; } @@ -1148,7 +1423,7 @@ static enum libusb_error process_new_device (struct libusb_context *ctx, struct dev->parent_dev = usbi_get_device_by_session_id (ctx, (unsigned long) cached_device->parent_session); } - (*(priv->dev->device))->GetDeviceSpeed (priv->dev->device, &devSpeed); + (*priv->dev->device)->GetDeviceSpeed (priv->dev->device, &devSpeed); switch (devSpeed) { case kUSBDeviceSpeedLow: dev->speed = LIBUSB_SPEED_LOW; break; @@ -1217,7 +1492,7 @@ static int darwin_open (struct libusb_device_handle *dev_handle) { if (0 == dpriv->open_count) { /* try to open the device */ - kresult = (*(dpriv->device))->USBDeviceOpenSeize (dpriv->device); + kresult = (*dpriv->device)->USBDeviceOpenSeize (dpriv->device); if (kresult != kIOReturnSuccess) { usbi_warn (HANDLE_CTX (dev_handle), "USBDeviceOpen: %s", darwin_error_str(kresult)); @@ -1232,12 +1507,13 @@ static int darwin_open (struct libusb_device_handle *dev_handle) { } /* create async event source */ - kresult = (*(dpriv->device))->CreateDeviceAsyncEventSource (dpriv->device, &priv->cfSource); + kresult = (*dpriv->device)->CreateDeviceAsyncEventSource (dpriv->device, + &priv->cfSource); if (kresult != kIOReturnSuccess) { usbi_err (HANDLE_CTX (dev_handle), "CreateDeviceAsyncEventSource: %s", darwin_error_str(kresult)); if (priv->is_open) { - (*(dpriv->device))->USBDeviceClose (dpriv->device); + (*dpriv->device)->USBDeviceClose (dpriv->device); } priv->is_open = false; @@ -1293,7 +1569,7 @@ static void darwin_close (struct libusb_device_handle *dev_handle) { if (priv->is_open) { /* close the device */ - kresult = (*(dpriv->device))->USBDeviceClose(dpriv->device); + kresult = (*dpriv->device)->USBDeviceClose(dpriv->device); if (kresult != kIOReturnSuccess) { /* Log the fact that we had a problem closing the file, however failing a * close isn't really an error, so return success anyway */ @@ -1325,7 +1601,7 @@ static enum libusb_error darwin_set_configuration(struct libusb_device_handle *d if (dev_handle->claimed_interfaces & (1U << i)) darwin_release_interface (dev_handle, i); - kresult = (*(dpriv->device))->SetConfiguration (dpriv->device, (UInt8)config); + kresult = (*dpriv->device)->SetConfiguration (dpriv->device, (UInt8)config); if (kresult != kIOReturnSuccess) return darwin_to_libusb (kresult); @@ -1339,7 +1615,7 @@ static enum libusb_error darwin_set_configuration(struct libusb_device_handle *d return LIBUSB_SUCCESS; } -static IOReturn darwin_get_interface (usb_device_t **darwin_device, uint8_t ifc, io_service_t *usbInterfacep) { +static IOReturn darwin_get_interface (usb_device_t darwin_device, uint8_t ifc, io_service_t *usbInterfacep) { IOUSBFindInterfaceRequest request; IOReturn kresult; io_iterator_t interface_iterator; @@ -1354,7 +1630,7 @@ static IOReturn darwin_get_interface (usb_device_t **darwin_device, uint8_t ifc, request.bInterfaceProtocol = kIOUSBFindInterfaceDontCare; request.bAlternateSetting = kIOUSBFindInterfaceDontCare; - kresult = (*(darwin_device))->CreateInterfaceIterator(darwin_device, &request, &interface_iterator); + kresult = (*darwin_device)->CreateInterfaceIterator(darwin_device, &request, &interface_iterator); if (kresult != kIOReturnSuccess) return kresult; @@ -1395,24 +1671,15 @@ static enum libusb_error get_endpoints (struct libusb_device_handle *dev_handle, /* current interface */ struct darwin_interface *cInterface = &priv->interfaces[iface]; -#if InterfaceVersion >= 550 - IOUSBEndpointProperties pipeProperties = {.bVersion = kUSBEndpointPropertiesVersion3}; -#else - UInt8 dont_care1, dont_care3; - UInt16 dont_care2; -#endif - IOReturn kresult; - - UInt8 numep, direction, number; + uint8_t numep; int rc; struct libusb_context *ctx = HANDLE_CTX (dev_handle); - usbi_dbg (ctx, "building table of endpoints."); /* retrieve the total number of endpoints on this interface */ - kresult = (*(cInterface->interface))->GetNumEndpoints(cInterface->interface, &numep); + kresult = (*IOINTERFACE(cInterface))->GetNumEndpoints(IOINTERFACE(cInterface), &numep); if (kresult != kIOReturnSuccess) { usbi_err (ctx, "can't get number of endpoints for interface: %s", darwin_error_str(kresult)); return darwin_to_libusb (kresult); @@ -1420,14 +1687,8 @@ static enum libusb_error get_endpoints (struct libusb_device_handle *dev_handle, /* iterate through pipe references */ for (UInt8 i = 1 ; i <= numep ; i++) { -#if InterfaceVersion >= 550 - kresult = (*(cInterface->interface))->GetPipePropertiesV3 (cInterface->interface, i, &pipeProperties); - number = pipeProperties.bEndpointNumber; - direction = pipeProperties.bDirection; -#else - kresult = (*(cInterface->interface))->GetPipeProperties(cInterface->interface, i, &direction, &number, &dont_care1, - &dont_care2, &dont_care3); -#endif + darwin_pipe_properties_t pipe_properties; + kresult = darwin_get_pipe_properties(cInterface, i, &pipe_properties); if (kresult != kIOReturnSuccess) { /* probably a buggy device. try to get the endpoint address from the descriptors */ struct libusb_config_descriptor *config; @@ -1435,7 +1696,7 @@ static enum libusb_error get_endpoints (struct libusb_device_handle *dev_handle, const struct libusb_endpoint_descriptor *endpoint_desc; UInt8 alt_setting; - kresult = (*(cInterface->interface))->GetAlternateSetting (cInterface->interface, &alt_setting); + kresult = (*IOINTERFACE(cInterface))->GetAlternateSetting (IOINTERFACE(cInterface), &alt_setting); if (kresult != kIOReturnSuccess) { usbi_err (HANDLE_CTX (dev_handle), "can't get alternate setting for interface"); return darwin_to_libusb (kresult); @@ -1457,7 +1718,8 @@ static enum libusb_error get_endpoints (struct libusb_device_handle *dev_handle, cInterface->endpoint_addrs[i - 1] = endpoint_desc->bEndpointAddress; libusb_free_config_descriptor (config); } else { - cInterface->endpoint_addrs[i - 1] = (UInt8)(((kUSBIn == direction) << kUSBRqDirnShift) | (number & LIBUSB_ENDPOINT_ADDRESS_MASK)); + cInterface->endpoint_addrs[i - 1] = (UInt8)(((kUSBIn == pipe_properties.direction) << kUSBRqDirnShift) | + (pipe_properties.number & LIBUSB_ENDPOINT_ADDRESS_MASK)); } usbi_dbg (ctx, "interface: %i pipe %i: dir: %i number: %i", iface, i, cInterface->endpoint_addrs[i - 1] >> kUSBRqDirnShift, @@ -1529,18 +1791,22 @@ static int darwin_claim_interface(struct libusb_device_handle *dev_handle, uint8 /* Do the actual claim */ kresult = (*plugInInterface)->QueryInterface(plugInInterface, - CFUUIDGetUUIDBytes(InterfaceInterfaceID), - (LPVOID)&cInterface->interface); + CFUUIDGetUUIDBytes(get_interface_interface_id()), + (LPVOID)&IOINTERFACE(cInterface)); /* We no longer need the intermediate plug-in */ /* Use release instead of IODestroyPlugInInterface to avoid stopping IOServices associated with this device */ (*plugInInterface)->Release (plugInInterface); - if (kresult != kIOReturnSuccess || !cInterface->interface) { + if (kresult != kIOReturnSuccess) { usbi_err (ctx, "QueryInterface: %s", darwin_error_str(kresult)); return darwin_to_libusb (kresult); } + if (!IOINTERFACE(cInterface)) { + usbi_err (ctx, "QueryInterface: returned null interface"); + return LIBUSB_ERROR_OTHER; + } /* claim the interface */ - kresult = (*(cInterface->interface))->USBInterfaceOpen(cInterface->interface); + kresult = (*IOINTERFACE(cInterface))->USBInterfaceOpen(IOINTERFACE(cInterface)); if (kresult != kIOReturnSuccess) { usbi_info (ctx, "USBInterfaceOpen: %s", darwin_error_str(kresult)); return darwin_to_libusb (kresult); @@ -1558,7 +1824,7 @@ static int darwin_claim_interface(struct libusb_device_handle *dev_handle, uint8 cInterface->cfSource = NULL; /* create async event source */ - kresult = (*(cInterface->interface))->CreateInterfaceAsyncEventSource (cInterface->interface, &cInterface->cfSource); + kresult = (*IOINTERFACE(cInterface))->CreateInterfaceAsyncEventSource (IOINTERFACE(cInterface), &cInterface->cfSource); if (kresult != kIOReturnSuccess) { usbi_err (ctx, "could not create async event source"); @@ -1584,8 +1850,9 @@ static int darwin_release_interface(struct libusb_device_handle *dev_handle, uin struct darwin_interface *cInterface = &priv->interfaces[iface]; /* Check to see if an interface is open */ - if (!cInterface->interface) + if (!IOINTERFACE(cInterface)) { return LIBUSB_SUCCESS; + } /* clean up endpoint data */ cInterface->num_endpoints = 0; @@ -1597,15 +1864,15 @@ static int darwin_release_interface(struct libusb_device_handle *dev_handle, uin cInterface->cfSource = NULL; } - kresult = (*(cInterface->interface))->USBInterfaceClose(cInterface->interface); + kresult = (*IOINTERFACE(cInterface))->USBInterfaceClose(IOINTERFACE(cInterface)); if (kresult != kIOReturnSuccess) usbi_warn (HANDLE_CTX (dev_handle), "USBInterfaceClose: %s", darwin_error_str(kresult)); - kresult = (*(cInterface->interface))->Release(cInterface->interface); + kresult = (*IOINTERFACE(cInterface))->Release(IOINTERFACE(cInterface)); if (kresult != kIOReturnSuccess) usbi_warn (HANDLE_CTX (dev_handle), "Release: %s", darwin_error_str(kresult)); - cInterface->interface = (usb_interface_t **) IO_OBJECT_NULL; + IOINTERFACE(cInterface) = NULL; return darwin_to_libusb (kresult); } @@ -1615,7 +1882,7 @@ static int check_alt_setting_and_clear_halt(struct libusb_device_handle *dev_han IOReturn kresult; uint8_t current_alt_setting; - kresult = (*(cInterface->interface))->GetAlternateSetting (cInterface->interface, ¤t_alt_setting); + kresult = (*IOINTERFACE(cInterface))->GetAlternateSetting (IOINTERFACE(cInterface), ¤t_alt_setting); if (kresult == kIOReturnSuccess && altsetting != current_alt_setting) { return LIBUSB_ERROR_PIPE; } @@ -1642,10 +1909,11 @@ static int darwin_set_interface_altsetting(struct libusb_device_handle *dev_hand /* current interface */ struct darwin_interface *cInterface = &priv->interfaces[iface]; - if (!cInterface->interface) + if (!IOINTERFACE(cInterface)) { return LIBUSB_ERROR_NO_DEVICE; + } - kresult = (*(cInterface->interface))->SetAlternateInterface (cInterface->interface, altsetting); + kresult = (*IOINTERFACE(cInterface))->SetAlternateInterface (IOINTERFACE(cInterface), altsetting); if (kresult == kIOReturnSuccess) { /* update the list of endpoints */ ret = get_endpoints (dev_handle, iface); @@ -1697,7 +1965,7 @@ static int darwin_clear_halt(struct libusb_device_handle *dev_handle, unsigned c } /* newer versions of darwin support clearing additional bits on the device's endpoint */ - kresult = (*(cInterface->interface))->ClearPipeStallBothEnds(cInterface->interface, pipeRef); + kresult = (*IOINTERFACE(cInterface))->ClearPipeStallBothEnds(IOINTERFACE(cInterface), pipeRef); if (kresult != kIOReturnSuccess) usbi_warn (HANDLE_CTX (dev_handle), "ClearPipeStall: %s", darwin_error_str (kresult)); @@ -1791,12 +2059,12 @@ static int darwin_reenumerate_device (struct libusb_device_handle *dev_handle, b cached_configurations = alloca (sizeof (*cached_configurations) * descriptor.bNumConfigurations); for (i = 0 ; i < descriptor.bNumConfigurations ; ++i) { - (*(dpriv->device))->GetConfigurationDescriptorPtr (dpriv->device, i, &cached_configuration); + (*dpriv->device)->GetConfigurationDescriptorPtr (dpriv->device, i, &cached_configuration); memcpy (cached_configurations + i, cached_configuration, sizeof (cached_configurations[i])); } /* if we need to release capture */ - if (HAS_CAPTURE_DEVICE()) { + if (get_running_version() >= 101000) { if (capture) { #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000 options |= kUSBReEnumerateCaptureDeviceMask; @@ -1807,7 +2075,7 @@ static int darwin_reenumerate_device (struct libusb_device_handle *dev_handle, b } /* from macOS 10.11 ResetDevice no longer does anything so just use USBDeviceReEnumerate */ - kresult = (*(dpriv->device))->USBDeviceReEnumerate (dpriv->device, options); + kresult = (*dpriv->device)->USBDeviceReEnumerate (dpriv->device, options); if (kresult != kIOReturnSuccess) { usbi_err (ctx, "USBDeviceReEnumerate: %s", darwin_error_str (kresult)); dpriv->in_reenumerate = false; @@ -1852,7 +2120,7 @@ static int darwin_reenumerate_device (struct libusb_device_handle *dev_handle, b } for (i = 0 ; i < descriptor.bNumConfigurations ; ++i) { - (void) (*(dpriv->device))->GetConfigurationDescriptorPtr (dpriv->device, i, &cached_configuration); + (void) (*dpriv->device)->GetConfigurationDescriptorPtr (dpriv->device, i, &cached_configuration); if (memcmp (cached_configuration, cached_configurations + i, sizeof (cached_configurations[i]))) { usbi_dbg (ctx, "darwin/reenumerate_device: configuration descriptor %d changed", i); return LIBUSB_ERROR_NOT_FOUND; @@ -1872,7 +2140,7 @@ static int darwin_reset_device (struct libusb_device_handle *dev_handle) { #if !defined(TARGET_OS_OSX) || TARGET_OS_OSX == 1 if (dpriv->capture_count > 0) { /* we have to use ResetDevice as USBDeviceReEnumerate() loses the authorization for capture */ - kresult = (*(dpriv->device))->ResetDevice (dpriv->device); + kresult = (*dpriv->device)->ResetDevice (dpriv->device); ret = darwin_to_libusb (kresult); } else { ret = darwin_reenumerate_device (dev_handle, false); @@ -1964,17 +2232,10 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer) { struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); IOReturn ret; - uint8_t transferType; uint8_t pipeRef; - uint16_t maxPacketSize; struct darwin_interface *cInterface; -#if InterfaceVersion >= 550 - IOUSBEndpointProperties pipeProperties = {.bVersion = kUSBEndpointPropertiesVersion3}; -#else - /* None of the values below are used in libusb for bulk transfers */ - uint8_t direction, number, interval; -#endif + darwin_pipe_properties_t pipe_properties; if (ep_to_pipeRef (transfer->dev_handle, transfer->endpoint, &pipeRef, NULL, &cInterface) != 0) { usbi_err (TRANSFER_CTX (transfer), "endpoint not found on any open interface"); @@ -1982,47 +2243,38 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer) { return LIBUSB_ERROR_NOT_FOUND; } -#if InterfaceVersion >= 550 - ret = (*(cInterface->interface))->GetPipePropertiesV3 (cInterface->interface, pipeRef, &pipeProperties); - - transferType = pipeProperties.bTransferType; - maxPacketSize = pipeProperties.wMaxPacketSize; -#else - ret = (*(cInterface->interface))->GetPipeProperties (cInterface->interface, pipeRef, &direction, &number, - &transferType, &maxPacketSize, &interval); -#endif - - if (ret) { + ret = darwin_get_pipe_properties(cInterface, pipeRef, &pipe_properties); + if (kIOReturnSuccess != ret) { usbi_err (TRANSFER_CTX (transfer), "bulk transfer failed (dir = %s): %s (code = 0x%08x)", IS_XFERIN(transfer) ? "In" : "Out", darwin_error_str(ret), ret); return darwin_to_libusb (ret); } - if (0 != (transfer->length % maxPacketSize)) { + if (0 != (transfer->length % pipe_properties.max_packet_size)) { /* do not need a zero packet */ transfer->flags &= ~LIBUSB_TRANSFER_ADD_ZERO_PACKET; } /* submit the request */ /* timeouts are unavailable on interrupt endpoints */ - if (transferType == kUSBInterrupt) { + if (pipe_properties.transfer_type == kUSBInterrupt) { if (IS_XFERIN(transfer)) - ret = (*(cInterface->interface))->ReadPipeAsync(cInterface->interface, pipeRef, transfer->buffer, - (UInt32)transfer->length, darwin_async_io_callback, itransfer); + ret = (*IOINTERFACE(cInterface))->ReadPipeAsync(IOINTERFACE(cInterface), pipeRef, transfer->buffer, + (UInt32)transfer->length, darwin_async_io_callback, itransfer); else - ret = (*(cInterface->interface))->WritePipeAsync(cInterface->interface, pipeRef, transfer->buffer, - (UInt32)transfer->length, darwin_async_io_callback, itransfer); + ret = (*IOINTERFACE(cInterface))->WritePipeAsync(IOINTERFACE(cInterface), pipeRef, transfer->buffer, + (UInt32)transfer->length, darwin_async_io_callback, itransfer); } else { itransfer->timeout_flags |= USBI_TRANSFER_OS_HANDLES_TIMEOUT; if (IS_XFERIN(transfer)) - ret = (*(cInterface->interface))->ReadPipeAsyncTO(cInterface->interface, pipeRef, transfer->buffer, - (UInt32)transfer->length, transfer->timeout, transfer->timeout, - darwin_async_io_callback, itransfer); + ret = (*IOINTERFACE(cInterface))->ReadPipeAsyncTO(IOINTERFACE(cInterface), pipeRef, transfer->buffer, + (UInt32)transfer->length, transfer->timeout, transfer->timeout, + darwin_async_io_callback, itransfer); else - ret = (*(cInterface->interface))->WritePipeAsyncTO(cInterface->interface, pipeRef, transfer->buffer, - (UInt32)transfer->length, transfer->timeout, transfer->timeout, - darwin_async_io_callback, itransfer); + ret = (*IOINTERFACE(cInterface))->WritePipeAsyncTO(IOINTERFACE(cInterface), pipeRef, transfer->buffer, + (UInt32)transfer->length, transfer->timeout, transfer->timeout, + darwin_async_io_callback, itransfer); } if (ret) @@ -2032,7 +2284,7 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer) { return darwin_to_libusb (ret); } -#if InterfaceVersion >= 550 +#if MAX_INTERFACE_VERSION >= 550 static int submit_stream_transfer(struct usbi_transfer *itransfer) { struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); struct darwin_interface *cInterface; @@ -2045,16 +2297,22 @@ static int submit_stream_transfer(struct usbi_transfer *itransfer) { return LIBUSB_ERROR_NOT_FOUND; } + if (get_interface_interface_version() < 550) { + usbi_err (TRANSFER_CTX(transfer), "IOUSBFamily version %d does not support bulk stream transfers", + get_interface_interface_version()); + return LIBUSB_ERROR_NOT_SUPPORTED; + } + itransfer->timeout_flags |= USBI_TRANSFER_OS_HANDLES_TIMEOUT; if (IS_XFERIN(transfer)) - ret = (*(cInterface->interface))->ReadStreamsPipeAsyncTO(cInterface->interface, pipeRef, itransfer->stream_id, - transfer->buffer, (UInt32)transfer->length, transfer->timeout, - transfer->timeout, darwin_async_io_callback, itransfer); + ret = (*IOINTERFACE_V(cInterface, 550))->ReadStreamsPipeAsyncTO(IOINTERFACE(cInterface), pipeRef, itransfer->stream_id, + transfer->buffer, (UInt32)transfer->length, transfer->timeout, + transfer->timeout, darwin_async_io_callback, itransfer); else - ret = (*(cInterface->interface))->WriteStreamsPipeAsyncTO(cInterface->interface, pipeRef, itransfer->stream_id, - transfer->buffer, (UInt32)transfer->length, transfer->timeout, - transfer->timeout, darwin_async_io_callback, itransfer); + ret = (*IOINTERFACE_V(cInterface, 550))->WriteStreamsPipeAsyncTO(IOINTERFACE(cInterface), pipeRef, itransfer->stream_id, + transfer->buffer, (UInt32)transfer->length, transfer->timeout, + transfer->timeout, darwin_async_io_callback, itransfer); if (ret) usbi_err (TRANSFER_CTX (transfer), "bulk stream transfer failed (dir = %s): %s (code = 0x%08x)", IS_XFERIN(transfer) ? "In" : "Out", @@ -2069,18 +2327,11 @@ static int submit_iso_transfer(struct usbi_transfer *itransfer) { struct darwin_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); IOReturn kresult; - uint8_t pipeRef, interval; + uint8_t pipeRef; UInt64 frame; AbsoluteTime atTime; int i; -#if InterfaceVersion >= 550 - IOUSBEndpointProperties pipeProperties = {.bVersion = kUSBEndpointPropertiesVersion3}; -#else - /* None of the values below are used in libusb for iso transfers */ - uint8_t direction, number, transferType; - uint16_t maxPacketSize; -#endif - + darwin_pipe_properties_t pipe_properties; struct darwin_interface *cInterface; /* construct an array of IOUSBIsocFrames, reuse the old one if the sizes are the same */ @@ -2111,13 +2362,7 @@ static int submit_iso_transfer(struct usbi_transfer *itransfer) { } /* determine the properties of this endpoint and the speed of the device */ -#if InterfaceVersion >= 550 - kresult = (*(cInterface->interface))->GetPipePropertiesV3 (cInterface->interface, pipeRef, &pipeProperties); - interval = pipeProperties.bInterval; -#else - kresult = (*(cInterface->interface))->GetPipeProperties (cInterface->interface, pipeRef, &direction, &number, - &transferType, &maxPacketSize, &interval); -#endif + kresult = darwin_get_pipe_properties(cInterface, pipeRef, &pipe_properties); if (kresult != kIOReturnSuccess) { usbi_err (TRANSFER_CTX (transfer), "failed to get pipe properties: %d", kresult); free(tpriv->isoc_framelist); @@ -2127,7 +2372,7 @@ static int submit_iso_transfer(struct usbi_transfer *itransfer) { } /* Last but not least we need the bus frame number */ - kresult = (*(cInterface->interface))->GetBusFrameNumber(cInterface->interface, &frame, &atTime); + kresult = (*IOINTERFACE(cInterface))->GetBusFrameNumber(IOINTERFACE(cInterface), &frame, &atTime); if (kresult != kIOReturnSuccess) { usbi_err (TRANSFER_CTX (transfer), "failed to get bus frame number: %d", kresult); free(tpriv->isoc_framelist); @@ -2144,20 +2389,20 @@ static int submit_iso_transfer(struct usbi_transfer *itransfer) { /* submit the request */ if (IS_XFERIN(transfer)) - kresult = (*(cInterface->interface))->ReadIsochPipeAsync(cInterface->interface, pipeRef, transfer->buffer, frame, - (UInt32)transfer->num_iso_packets, tpriv->isoc_framelist, darwin_async_io_callback, - itransfer); + kresult = (*IOINTERFACE(cInterface))->ReadIsochPipeAsync(IOINTERFACE(cInterface), pipeRef, transfer->buffer, frame, + (UInt32)transfer->num_iso_packets, tpriv->isoc_framelist, darwin_async_io_callback, + itransfer); else - kresult = (*(cInterface->interface))->WriteIsochPipeAsync(cInterface->interface, pipeRef, transfer->buffer, frame, - (UInt32)transfer->num_iso_packets, tpriv->isoc_framelist, darwin_async_io_callback, - itransfer); + kresult = (*IOINTERFACE(cInterface))->WriteIsochPipeAsync(IOINTERFACE(cInterface), pipeRef, transfer->buffer, frame, + (UInt32)transfer->num_iso_packets, tpriv->isoc_framelist, darwin_async_io_callback, + itransfer); if (LIBUSB_SPEED_FULL == transfer->dev_handle->dev->speed) /* Full speed */ - cInterface->frames[transfer->endpoint] = frame + (UInt32)transfer->num_iso_packets * (1U << (interval - 1)); + cInterface->frames[transfer->endpoint] = frame + (UInt32)transfer->num_iso_packets * (1U << (pipe_properties.interval - 1)); else /* High/super speed */ - cInterface->frames[transfer->endpoint] = frame + (UInt32)transfer->num_iso_packets * (1U << (interval - 1)) / 8; + cInterface->frames[transfer->endpoint] = frame + (UInt32)transfer->num_iso_packets * (1U << (pipe_properties.interval - 1)) / 8; if (kresult != kIOReturnSuccess) { usbi_err (TRANSFER_CTX (transfer), "isochronous transfer failed (dir: %s): %s", IS_XFERIN(transfer) ? "In" : "Out", @@ -2205,10 +2450,11 @@ static int submit_control_transfer(struct usbi_transfer *itransfer) { return LIBUSB_ERROR_NOT_FOUND; } - kresult = (*(cInterface->interface))->ControlRequestAsyncTO (cInterface->interface, pipeRef, &(tpriv->req), darwin_async_io_callback, itransfer); + kresult = (*IOINTERFACE(cInterface))->ControlRequestAsyncTO (IOINTERFACE(cInterface), pipeRef, + &(tpriv->req), darwin_async_io_callback, itransfer); } else /* control request on endpoint 0 */ - kresult = (*(dpriv->device))->DeviceRequestAsyncTO(dpriv->device, &(tpriv->req), darwin_async_io_callback, itransfer); + kresult = (*dpriv->device)->DeviceRequestAsyncTO(dpriv->device, &(tpriv->req), darwin_async_io_callback, itransfer); if (kresult != kIOReturnSuccess) usbi_err (TRANSFER_CTX (transfer), "control request failed: %s", darwin_error_str(kresult)); @@ -2228,7 +2474,7 @@ static int darwin_submit_transfer(struct usbi_transfer *itransfer) { case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: return submit_iso_transfer(itransfer); case LIBUSB_TRANSFER_TYPE_BULK_STREAM: -#if InterfaceVersion >= 550 +#if MAX_INTERFACE_VERSION >= 550 return submit_stream_transfer(itransfer); #else usbi_err (TRANSFER_CTX(transfer), "IOUSBFamily version does not support bulk stream transfers"); @@ -2247,10 +2493,11 @@ static int cancel_control_transfer(struct usbi_transfer *itransfer) { usbi_warn (ITRANSFER_CTX (itransfer), "aborting all transactions control pipe"); - if (!dpriv->device) + if (!dpriv->device) { return LIBUSB_ERROR_NO_DEVICE; + } - kresult = (*(dpriv->device))->USBDeviceAbortPipeZero (dpriv->device); + kresult = (*dpriv->device)->USBDeviceAbortPipeZero (dpriv->device); return darwin_to_libusb (kresult); } @@ -2270,25 +2517,29 @@ static int darwin_abort_transfers (struct usbi_transfer *itransfer) { return LIBUSB_ERROR_NOT_FOUND; } - if (!dpriv->device) + if (!dpriv->device) { return LIBUSB_ERROR_NO_DEVICE; + } usbi_warn (ctx, "aborting all transactions on interface %d pipe %d", iface, pipeRef); /* abort transactions */ -#if InterfaceVersion >= 550 - if (LIBUSB_TRANSFER_TYPE_BULK_STREAM == transfer->type) - kresult = (*(cInterface->interface))->AbortStreamsPipe (cInterface->interface, pipeRef, itransfer->stream_id); - else +#if MAX_INTERFACE_VERSION >= 550 + if (LIBUSB_TRANSFER_TYPE_BULK_STREAM == transfer->type && get_interface_interface_version() >= 550) { + kresult = (*IOINTERFACE_V(cInterface, 550))->AbortStreamsPipe (IOINTERFACE(cInterface), pipeRef, itransfer->stream_id); + } else #endif - kresult = (*(cInterface->interface))->AbortPipe (cInterface->interface, pipeRef); + { + kresult = (*IOINTERFACE(cInterface))->AbortPipe (IOINTERFACE(cInterface), pipeRef); + } -#if InterfaceVersion <= 245 - /* with older releases of IOUSBFamily the OS always clears the host side data toggle. for - consistency also clear the data toggle on the device. */ - usbi_dbg (ctx, "calling ClearPipeStallBothEnds to clear the data toggle bit"); - kresult = (*(cInterface->interface))->ClearPipeStallBothEnds(cInterface->interface, pipeRef); -#endif + + if (get_interface_interface_version() <= 245) { + /* with older releases of IOUSBFamily the OS always clears the host side data toggle. for + consistency also clear the data toggle on the device. */ + usbi_dbg (ctx, "calling ClearPipeStallBothEnds to clear the data toggle bit"); + kresult = (*IOINTERFACE(cInterface))->ClearPipeStallBothEnds(IOINTERFACE(cInterface), pipeRef); + } return darwin_to_libusb (kresult); } @@ -2323,7 +2574,7 @@ static void darwin_async_io_callback (void *refcon, IOReturn result, void *arg0) (void) ep_to_pipeRef (transfer->dev_handle, transfer->endpoint, &pipeRef, NULL, &cInterface); - (*(cInterface->interface))->WritePipe (cInterface->interface, pipeRef, transfer->buffer, 0); + (*IOINTERFACE(cInterface))->WritePipe (IOINTERFACE(cInterface), pipeRef, transfer->buffer, 0); } tpriv->result = result; @@ -2430,7 +2681,7 @@ void usbi_get_real_time(struct timespec *tp) { #endif } -#if InterfaceVersion >= 550 +#if MAX_INTERFACE_VERSION >= 550 static int darwin_alloc_streams (struct libusb_device_handle *dev_handle, uint32_t num_streams, unsigned char *endpoints, int num_endpoints) { struct darwin_interface *cInterface; @@ -2444,7 +2695,7 @@ static int darwin_alloc_streams (struct libusb_device_handle *dev_handle, uint32 return rc; } - (*(cInterface->interface))->SupportsStreams (cInterface->interface, pipeRef, &supportsStreams); + (*IOINTERFACE_V(cInterface, 550))->SupportsStreams (IOINTERFACE(cInterface), pipeRef, &supportsStreams); if (num_streams > supportsStreams) num_streams = supportsStreams; } @@ -2457,7 +2708,7 @@ static int darwin_alloc_streams (struct libusb_device_handle *dev_handle, uint32 for (i = 0 ; i < num_endpoints ; ++i) { (void) ep_to_pipeRef (dev_handle, endpoints[i], &pipeRef, NULL, &cInterface); - rc = (*(cInterface->interface))->CreateStreams (cInterface->interface, pipeRef, num_streams); + rc = (*IOINTERFACE_V(cInterface, 550))->CreateStreams (IOINTERFACE(cInterface), pipeRef, num_streams); if (kIOReturnSuccess != rc) return darwin_to_libusb(rc); } @@ -2476,11 +2727,11 @@ static int darwin_free_streams (struct libusb_device_handle *dev_handle, unsigne if (0 != (rc = ep_to_pipeRef (dev_handle, endpoints[i], &pipeRef, NULL, &cInterface))) return rc; - (*(cInterface->interface))->SupportsStreams (cInterface->interface, pipeRef, &supportsStreams); + (*IOINTERFACE_V(cInterface, 550))->SupportsStreams (IOINTERFACE(cInterface), pipeRef, &supportsStreams); if (0 == supportsStreams) return LIBUSB_ERROR_INVALID_PARAM; - rc = (*(cInterface->interface))->CreateStreams (cInterface->interface, pipeRef, 0); + rc = (*IOINTERFACE_V(cInterface, 550))->CreateStreams (IOINTERFACE(cInterface), pipeRef, 0); if (kIOReturnSuccess != rc) return darwin_to_libusb(rc); } @@ -2489,7 +2740,7 @@ static int darwin_free_streams (struct libusb_device_handle *dev_handle, unsigne } #endif -#if InterfaceVersion >= 700 +#if MAX_INTERFACE_VERSION >= 700 /* macOS APIs for getting entitlement values */ @@ -2524,13 +2775,8 @@ static int darwin_reload_device (struct libusb_device_handle *dev_handle) { enum libusb_error err; usbi_mutex_lock(&darwin_cached_devices_mutex); - (*(dpriv->device))->Release(dpriv->device); - dpriv->device = darwin_device_from_service (HANDLE_CTX (dev_handle), dpriv->service); - if (!dpriv->device) { - err = LIBUSB_ERROR_NO_DEVICE; - } else { - err = LIBUSB_SUCCESS; - } + (*dpriv->device)->Release(dpriv->device); + err = darwin_device_from_service (HANDLE_CTX (dev_handle), dpriv->service, &dpriv->device); usbi_mutex_unlock(&darwin_cached_devices_mutex); return err; @@ -2545,8 +2791,7 @@ static int darwin_detach_kernel_driver (struct libusb_device_handle *dev_handle, enum libusb_error err; struct libusb_context *ctx = HANDLE_CTX (dev_handle); - if (HAS_CAPTURE_DEVICE()) { - } else { + if (get_interface_interface_version() < 700) { return LIBUSB_ERROR_NOT_SUPPORTED; } @@ -2589,8 +2834,7 @@ static int darwin_attach_kernel_driver (struct libusb_device_handle *dev_handle, UNUSED(interface); struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); - if (HAS_CAPTURE_DEVICE()) { - } else { + if (get_interface_interface_version() < 700) { return LIBUSB_ERROR_NOT_SUPPORTED; } @@ -2657,14 +2901,14 @@ const struct usbi_os_backend usbi_backend = { .clear_halt = darwin_clear_halt, .reset_device = darwin_reset_device, -#if InterfaceVersion >= 550 +#if MAX_INTERFACE_VERSION >= 550 .alloc_streams = darwin_alloc_streams, .free_streams = darwin_free_streams, #endif .kernel_driver_active = darwin_kernel_driver_active, -#if InterfaceVersion >= 700 +#if MAX_INTERFACE_VERSION >= 700 .detach_kernel_driver = darwin_detach_kernel_driver, .attach_kernel_driver = darwin_attach_kernel_driver, .claim_interface = darwin_capture_claim_interface, diff --git a/libusb/libusb/os/darwin_usb.h b/libusb/libusb/os/darwin_usb.h index 7b72fff..86646bf 100644 --- a/libusb/libusb/os/darwin_usb.h +++ b/libusb/libusb/os/darwin_usb.h @@ -1,7 +1,7 @@ /* * darwin backend for libusb 1.0 - * Copyright © 2008-2019 Nathan Hjelm - * Copyright © 2019 Google LLC. All rights reserved. + * Copyright © 2008-2023 Nathan Hjelm + * Copyright © 2019-2023 Google LLC. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -36,117 +36,48 @@ /* IOUSBInterfaceInferface */ -/* New in OS 10.12.0. */ -#if defined (kIOUSBInterfaceInterfaceID800) - -#define usb_interface_t IOUSBInterfaceInterface800 -#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID800 -#define InterfaceVersion 800 - -/* New in OS 10.10.0. */ -#elif defined (kIOUSBInterfaceInterfaceID700) - -#define usb_interface_t IOUSBInterfaceInterface700 -#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID700 -#define InterfaceVersion 700 - -/* New in OS 10.9.0. */ -#elif defined (kIOUSBInterfaceInterfaceID650) - -#define usb_interface_t IOUSBInterfaceInterface650 -#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID650 -#define InterfaceVersion 650 - -/* New in OS 10.8.2 but can't test deployment target to that granularity, so round up. */ -#elif defined (kIOUSBInterfaceInterfaceID550) - -#define usb_interface_t IOUSBInterfaceInterface550 -#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID550 -#define InterfaceVersion 550 - -/* New in OS 10.7.3 but can't test deployment target to that granularity, so round up. */ -#elif defined (kIOUSBInterfaceInterfaceID500) - -#define usb_interface_t IOUSBInterfaceInterface500 -#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID500 -#define InterfaceVersion 500 - -/* New in OS 10.5.0. */ -#elif defined (kIOUSBInterfaceInterfaceID300) - -#define usb_interface_t IOUSBInterfaceInterface300 -#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID300 -#define InterfaceVersion 300 - -/* New in OS 10.4.5 (or 10.4.6?) but can't test deployment target to that granularity, so round up. */ -#elif defined (kIOUSBInterfaceInterfaceID245) - -#define usb_interface_t IOUSBInterfaceInterface245 -#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID245 -#define InterfaceVersion 245 - -/* New in OS 10.4.0. */ -#elif defined (kIOUSBInterfaceInterfaceID220) - -#define usb_interface_t IOUSBInterfaceInterface220 -#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID220 -#define InterfaceVersion 220 - +#if defined(kIOUSBInterfaceInterfaceID800) +#define MAX_INTERFACE_VERSION 800 +#elif defined(kIOUSBInterfaceInterfaceID700) +#define MAX_INTERFACE_VERSION 700 +#elif defined(kIOUSBInterfaceInterfaceID650) +#define MAX_INTERFACE_VERSION 650 +#elif defined(kIOUSBInterfaceInterfaceID550) +#define MAX_INTERFACE_VERSION 550 +#elif defined(kIOUSBInterfaceInterfaceID245) +#define MAX_INTERFACE_VERSION 245 #else - -#error "IOUSBFamily is too old. Please upgrade your SDK and/or deployment target" - +#define MAX_INTERFACE_VERSION 220 #endif -/* IOUSBDeviceInterface */ - -/* New in OS 10.9.0. */ -#if defined (kIOUSBDeviceInterfaceID650) - -#define usb_device_t IOUSBDeviceInterface650 -#define DeviceInterfaceID kIOUSBDeviceInterfaceID650 -#define DeviceVersion 650 - -/* New in OS 10.7.3 but can't test deployment target to that granularity, so round up. */ -#elif defined (kIOUSBDeviceInterfaceID500) - -#define usb_device_t IOUSBDeviceInterface500 -#define DeviceInterfaceID kIOUSBDeviceInterfaceID500 -#define DeviceVersion 500 - -/* New in OS 10.5.4 but can't test deployment target to that granularity, so round up. */ -#elif defined (kIOUSBDeviceInterfaceID320) - -#define usb_device_t IOUSBDeviceInterface320 -#define DeviceInterfaceID kIOUSBDeviceInterfaceID320 -#define DeviceVersion 320 - -/* New in OS 10.5.0. */ -#elif defined (kIOUSBDeviceInterfaceID300) - -#define usb_device_t IOUSBDeviceInterface300 -#define DeviceInterfaceID kIOUSBDeviceInterfaceID300 -#define DeviceVersion 300 +/* set to the minimum version and casted up as needed. */ +typedef IOUSBInterfaceInterface220 **usb_interface_t; -/* New in OS 10.4.5 (or 10.4.6?) but can't test deployment target to that granularity, so round up. */ -#elif defined (kIOUSBDeviceInterfaceID245) +#define IOINTERFACE0(darwin_interface, version) ((IOUSBInterfaceInterface ## version **) (darwin_interface)->interface) +#define IOINTERFACE_V(darwin_interface, version) IOINTERFACE0(darwin_interface, version) +#define IOINTERFACE(darwin_interface) ((darwin_interface)->interface) -#define usb_device_t IOUSBDeviceInterface245 -#define DeviceInterfaceID kIOUSBDeviceInterfaceID245 -#define DeviceVersion 245 - -/* New in OS 10.2.3 but can't test deployment target to that granularity, so round up. */ -#elif defined (kIOUSBDeviceInterfaceID197) - -#define usb_device_t IOUSBDeviceInterface197 -#define DeviceInterfaceID kIOUSBDeviceInterfaceID197 -#define DeviceVersion 197 +/* IOUSBDeviceInterface */ +#if defined(kIOUSBDeviceInterfaceID650) +#define MAX_DEVICE_VERSION 650 +#elif defined(kIOUSBDeviceInterfaceID500) +#define MAX_DEVICE_VERSION 500 +#elif defined(kIOUSBDeviceInterfaceID320) +#define MAX_DEVICE_VERSION 320 +#elif defined(kIOUSBDeviceInterfaceID300) +#define MAX_DEVICE_VERSION 300 +#elif defined(kIOUSBDeviceInterfaceID245) +#define MAX_DEVICE_VERSION 245 #else +#define MAX_DEVICE_VERSION 197 +#endif -#error "IOUSBFamily is too old. Please upgrade your SDK and/or deployment target" +/* set to the minimum version and casted up as needed */ +typedef IOUSBDeviceInterface197 **usb_device_t; -#endif +#define IODEVICE0(darwin_device, version) ((IOUSBDeviceInterface ## version **)(darwin_device)) +#define IODEVICE_V(darwin_device, version) IODEVICE0(darwin_device, version) #if !defined(kIOUSBHostInterfaceClassName) #define kIOUSBHostInterfaceClassName "IOUSBHostInterface" @@ -160,15 +91,13 @@ #define IO_OBJECT_NULL ((io_object_t) 0) #endif -/* Testing availability */ -#ifndef __has_builtin - #define __has_builtin(x) 0 // Compatibility with non-clang compilers. -#endif -#if __has_builtin(__builtin_available) - #define HAS_CAPTURE_DEVICE() __builtin_available(macOS 10.10, *) -#else - #define HAS_CAPTURE_DEVICE() 0 -#endif +/* returns the current macOS version in a format similar to the + * MAC_OS_X_VERSION_MIN_REQUIRED macro. + * Examples: + * 10.1.5 -> 100105 + * 13.3.0 -> 130300 + */ +uint32_t get_running_version(void); typedef IOCFPlugInInterface *io_cf_plugin_ref_t; typedef IONotificationPortRef io_notification_port_t; @@ -182,7 +111,7 @@ struct darwin_cached_device { UInt64 session; USBDeviceAddress address; char sys_path[21]; - usb_device_t **device; + usb_device_t device; io_service_t service; int open_count; UInt8 first_config, active_config, port; @@ -201,7 +130,7 @@ struct darwin_device_handle_priv { CFRunLoopSourceRef cfSource; struct darwin_interface { - usb_interface_t **interface; + usb_interface_t interface; uint8_t num_endpoints; CFRunLoopSourceRef cfSource; uint64_t frames[256]; diff --git a/libusb/libusb/os/emscripten_webusb.cpp b/libusb/libusb/os/emscripten_webusb.cpp index 325a3a1..f19c1bd 100644 --- a/libusb/libusb/os/emscripten_webusb.cpp +++ b/libusb/libusb/os/emscripten_webusb.cpp @@ -1,5 +1,6 @@ /* * Copyright © 2021 Google LLC + * Copyright © 2023 Ingvar Stepanyan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -19,23 +20,47 @@ * Ingvar Stepanyan */ +#include + +static_assert((__EMSCRIPTEN_major__ * 100 * 100 + __EMSCRIPTEN_minor__ * 100 + + __EMSCRIPTEN_tiny__) >= 30148, + "Emscripten 3.1.48 or newer is required."); + +#include #include #include +#include +#include + #include "libusbi.h" using namespace emscripten; +#ifdef _REENTRANT +#include +#include +#include + +static ProxyingQueue queue; +#endif + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wmissing-prototypes" #pragma clang diagnostic ignored "-Wunused-parameter" +#pragma clang diagnostic ignored "-Wshadow" + namespace { + // clang-format off - EM_JS(EM_VAL, em_promise_catch_impl, (EM_VAL handle), { - let promise = Emval.toValue(handle); - promise = promise.then( - value => ({error : 0, value}), - error => { +EM_JS(EM_VAL, usbi_em_promise_catch, (EM_VAL handle), { + let promise = Emval.toValue(handle); + promise = promise.then( + value => ({error : 0, value}), + error => { + console.error(error); + let errorCode = -99; // LIBUSB_ERROR_OTHER + if (error instanceof DOMException) { const ERROR_CODES = { // LIBUSB_ERROR_IO NetworkError : -1, @@ -56,571 +81,790 @@ namespace { // LIBUSB_ERROR_NOT_SUPPORTED NotSupportedError : -12, }; - console.error(error); - let errorCode = -99; // LIBUSB_ERROR_OTHER - if (error instanceof DOMException) - { - errorCode = ERROR_CODES[error.name] ?? errorCode; - } - else if ((error instanceof RangeError) || (error instanceof TypeError)) - { - errorCode = -2; // LIBUSB_ERROR_INVALID_PARAM - } - return {error: errorCode, value: undefined}; + errorCode = ERROR_CODES[error.name] ?? errorCode; + } else if (error instanceof RangeError || error instanceof TypeError) { + errorCode = -2; // LIBUSB_ERROR_INVALID_PARAM + } + return {error: errorCode, value: undefined}; + } + ); + return Emval.toHandle(promise); +}); + +EM_JS(void, usbi_em_copy_from_dataview, (void* dst, EM_VAL src), { + src = Emval.toValue(src); + src = new Uint8Array(src.buffer, src.byteOffset, src.byteLength); + HEAPU8.set(src, dst); +}); + +// Our implementation proxies operations from multiple threads to the same +// underlying USBDevice on the main thread. This can lead to issues when +// multiple threads try to open/close the same device at the same time. +// +// First, since open/close operations are asynchronous in WebUSB, we can end up +// with multiple open/close operations in flight at the same time, which can +// lead to unpredictable outcome (e.g. device got closed but opening succeeded +// right before that). +// +// Second, since multiple threads are allowed to have their own handles to the +// same device, we need to keep track of number of open handles and close the +// device only when the last handle is closed. +// +// We fix both of these issues by using a shared promise chain that executes +// open and close operations sequentially and keeps track of the reference count +// in each promise's result. This way, we can ensure that only one open/close +// operation is in flight at any given time. Note that we don't need to worry +// about all other operations because they're preconditioned on the device being +// open and having at least 1 reference anyway. +EM_JS(EM_VAL, usbi_em_device_safe_open_close, (EM_VAL device, bool open), { + device = Emval.toValue(device); + const symbol = Symbol.for('libusb.open_close_chain'); + let promiseChain = device[symbol] ?? Promise.resolve(0); + device[symbol] = promiseChain = promiseChain.then(async refCount => { + if (open) { + if (!refCount++) { + await device.open(); } - ); - return Emval.toHandle(promise); + } else { + if (!--refCount) { + await device.close(); + } + } + return refCount; }); + return Emval.toHandle(promiseChain); +}); // clang-format on -val em_promise_catch(val &&promise) { - EM_VAL handle = promise.as_handle(); - handle = em_promise_catch_impl(handle); - return val::take_ownership(handle); +libusb_transfer_status getTransferStatus(const val& transfer_result) { + auto status = transfer_result["status"].as(); + if (status == "ok") { + return LIBUSB_TRANSFER_COMPLETED; + } else if (status == "stall") { + return LIBUSB_TRANSFER_STALL; + } else if (status == "babble") { + return LIBUSB_TRANSFER_OVERFLOW; + } else { + return LIBUSB_TRANSFER_ERROR; + } +} + +// Note: this assumes that `dst` is valid for at least `src.byteLength` bytes. +// This is true for all results returned from WebUSB as we pass max length to +// the transfer APIs. +void copyFromDataView(void* dst, const val& src) { + usbi_em_copy_from_dataview(dst, src.as_handle()); +} + +auto getUnsharedMemoryView(void* src, size_t len) { + auto view = typed_memory_view(len, (uint8_t*)src); +#ifdef _REENTRANT + // Unfortunately, TypedArrays backed by SharedArrayBuffers are not accepted + // by most Web APIs, trading off guaranteed thread-safety for performance + // loss. The usual workaround is to copy them into a new TypedArray, which + // is what we do here via the `.slice()` method. + return val(view).call("slice"); +#else + // Non-threaded builds can avoid the copy penalty. + return view; +#endif +} + +// A helper that proxies a function call to the main thread if not already +// there. This is a wrapper around Emscripten's raw proxying API with couple of +// high-level improvements, namely support for destroying lambda on the target +// thread as well as custom return types. +template +auto runOnMain(Func&& func) { +#ifdef _REENTRANT + if (!emscripten_is_main_runtime_thread()) { + if constexpr (std::is_same_v, void>) { + bool proxied = + queue.proxySync(emscripten_main_runtime_thread_id(), [&func] { + // Capture func by reference and move into a local variable + // to render the captured func inert on the first (and only) + // call. This way it can be safely destructed on the main + // thread instead of the current one when this call + // finishes. TODO: remove this when + // https://github.com/emscripten-core/emscripten/issues/20610 + // is fixed. + auto func_ = std::move(func); + func_(); + }); + assert(proxied); + return; + } else { + // A storage for the result of the function call. + // TODO: remove when + // https://github.com/emscripten-core/emscripten/issues/20611 is + // implemented. + std::optional> result; + runOnMain( + [&result, func = std::move(func)] { result.emplace(func()); }); + return std::move(result.value()); + } + } +#endif + return func(); } -// C++ struct representation for {value, error} object from above -// (performs conversion in the constructor). -struct promise_result { - libusb_error error; - val value; - - promise_result(val &&result) - : error(static_cast(result["error"].as())), - value(result["value"]) {} - - // C++ counterpart of the promise helper above that takes a promise, catches - // its error, converts to a libusb status and returns the whole thing as - // `promise_result` struct for easier handling. - static promise_result await(val &&promise) { - promise = em_promise_catch(std::move(promise)); - return {promise.await()}; - } +// C++ struct representation for `{value, error}` object used by `CaughtPromise` +// below. +struct PromiseResult { + int error; + val value; + + PromiseResult() = delete; + PromiseResult(PromiseResult&&) = default; + + PromiseResult(val&& result) + : error(result["error"].as()), value(result["value"]) {} + + ~PromiseResult() { + // make sure value is freed on the thread it exists on + runOnMain([value = std::move(value)] {}); + } }; -// We store an Embind handle to WebUSB USBDevice in "priv" metadata of -// libusb device, this helper returns a pointer to it. +struct CaughtPromise : val { + CaughtPromise(val&& promise) + : val(wrapPromiseWithCatch(std::move(promise))) {} + + using AwaitResult = PromiseResult; + +private: + + // Wrap promise with conversion from some value T to `{value: T, error: + // number}`. + static val wrapPromiseWithCatch(val&& promise) { + auto handle = promise.as_handle(); + handle = usbi_em_promise_catch(handle); + return val::take_ownership(handle); + } +}; + +#define co_await_try(promise) \ + ({ \ + PromiseResult result = co_await CaughtPromise(promise); \ + if (result.error) { \ + co_return result.error; \ + } \ + std::move(result.value); \ + }) + +// A helper that runs an asynchronous callback when the promise is resolved. +template +val promiseThen(Promise&& promise, OnResult&& onResult) { + // Save captures from the callback while we can, or they'll be destructed. + // https://devblogs.microsoft.com/oldnewthing/20211103-00/?p=105870 + auto onResult_ = std::move(onResult); + onResult_(co_await promise); + co_return val::undefined(); +} + +// A helper that runs an asynchronous function on the main thread and blocks the +// current thread until the promise is resolved (via Asyncify "blocking" if +// already on the main thread or regular blocking otherwise). +template +static std::invoke_result_t::AwaitResult awaitOnMain(Func&& func) { +#ifdef _REENTRANT + if (!emscripten_is_main_runtime_thread()) { + // If we're on a different thread, we can't use main thread's Asyncify + // as multiple threads might be fighting for its state; instead, use + // proxying to synchronously block the current thread until the promise + // is complete. + std::optional::AwaitResult> result; + queue.proxySyncWithCtx( + emscripten_main_runtime_thread_id(), + [&result, &func](ProxyingQueue::ProxyingCtx ctx) { + // Same as `func` in `runOnMain`, move to destruct on the first + // call. + auto func_ = std::move(func); + promiseThen( + func_(), + [&result, ctx = std::move(ctx)](auto&& result_) mutable { + result.emplace(std::move(result_)); + ctx.finish(); + }); + }); + return std::move(result.value()); + } +#endif + // If we're already on the main thread, use Asyncify to block until the + // promise is resolved. + return func().await(); +} + +// A helper that makes a control transfer given a setup pointer (assumed to be +// followed by data payload for out-transfers). +val makeControlTransferPromise(const val& dev, libusb_control_setup* setup) { + auto params = val::object(); + + const char* request_type = "unknown"; + // See LIBUSB_REQ_TYPE in windows_winusb.h (or docs for `bmRequestType`). + switch (setup->bmRequestType & (0x03 << 5)) { + case LIBUSB_REQUEST_TYPE_STANDARD: + request_type = "standard"; + break; + case LIBUSB_REQUEST_TYPE_CLASS: + request_type = "class"; + break; + case LIBUSB_REQUEST_TYPE_VENDOR: + request_type = "vendor"; + break; + } + params.set("requestType", request_type); + + const char* recipient = "other"; + switch (setup->bmRequestType & 0x0f) { + case LIBUSB_RECIPIENT_DEVICE: + recipient = "device"; + break; + case LIBUSB_RECIPIENT_INTERFACE: + recipient = "interface"; + break; + case LIBUSB_RECIPIENT_ENDPOINT: + recipient = "endpoint"; + break; + } + params.set("recipient", recipient); + + params.set("request", setup->bRequest); + params.set("value", setup->wValue); + params.set("index", setup->wIndex); + + if (setup->bmRequestType & LIBUSB_ENDPOINT_IN) { + return dev.call("controlTransferIn", params, setup->wLength); + } else { + return dev.call("controlTransferOut", params, + getUnsharedMemoryView(setup + 1, setup->wLength)); + } +} + +// Smart pointer for managing pointers to places allocated by libusb inside its +// backend structures. +template struct ValPtr { - public: - void init_to(val &&value) { new (ptr) val(std::move(value)); } + template + void emplace(Args&&... args) { + new (ptr) T(std::forward(args)...); + } + + const T& operator*() const { return *ptr; } + T& operator*() { return *ptr; } + + const T* operator->() const { return ptr; } + T* operator->() { return ptr; } + + void free() { ptr->~T(); } - val &get() { return *ptr; } - val take() { return std::move(get()); } + T take() { + auto value = std::move(*ptr); + free(); + return value; + } - protected: - ValPtr(val *ptr) : ptr(ptr) {} +protected: - private: - val *ptr; + ValPtr(void* ptr) : ptr(static_cast(ptr)) {} + +private: + + // Note: this is not a heap-allocated pointer, but a pointer to a part + // of the backend structure allocated by libusb itself. + T* ptr; }; -struct WebUsbDevicePtr : ValPtr { - public: - WebUsbDevicePtr(libusb_device *dev) - : ValPtr(static_cast(usbi_get_device_priv(dev))) {} +struct CachedDevice; + +struct WebUsbDevicePtr : ValPtr { +public: + + WebUsbDevicePtr(libusb_device* dev) : ValPtr(usbi_get_device_priv(dev)) {} + WebUsbDevicePtr(libusb_device_handle* handle) + : WebUsbDevicePtr(handle->dev) {} }; -val &get_web_usb_device(libusb_device *dev) { - return WebUsbDevicePtr(dev).get(); -} +struct WebUsbTransferPtr : ValPtr { +public: -struct WebUsbTransferPtr : ValPtr { - public: - WebUsbTransferPtr(usbi_transfer *itransfer) - : ValPtr(static_cast(usbi_get_transfer_priv(itransfer))) {} + WebUsbTransferPtr(usbi_transfer* itransfer) + : ValPtr(usbi_get_transfer_priv(itransfer)) {} }; -void em_signal_transfer_completion_impl(usbi_transfer *itransfer, - val &&result) { - WebUsbTransferPtr(itransfer).init_to(std::move(result)); - usbi_signal_transfer_completion(itransfer); -} +enum class OpenClose : bool { + Open = true, + Close = false, +}; + +struct CachedDevice { + CachedDevice() = delete; + CachedDevice(CachedDevice&&) = delete; + + // Fill in the device descriptor and configurations by reading them from the + // WebUSB device. + static val initFromDevice(val&& web_usb_dev, libusb_device* libusb_dev) { + auto cachedDevicePtr = WebUsbDevicePtr(libusb_dev); + cachedDevicePtr.emplace(std::move(web_usb_dev)); + bool must_close = false; + val result = co_await cachedDevicePtr->initFromDeviceWithoutClosing( + libusb_dev, must_close); + if (must_close) { + co_await_try(cachedDevicePtr->safeOpenCloseAssumingMainThread( + OpenClose::Close)); + } + co_return std::move(result); + } + + const val& getDeviceAssumingMainThread() const { return device; } + + uint8_t getActiveConfigValue() const { + return runOnMain([&] { + auto web_usb_config = device["configuration"]; + return web_usb_config.isNull() + ? 0 + : web_usb_config["configurationValue"].as(); + }); + } + + usbi_configuration_descriptor* getConfigDescriptor(uint8_t config_id) { + return config_id < configurations.size() + ? configurations[config_id].get() + : nullptr; + } + + usbi_configuration_descriptor* findConfigDescriptorByValue( + uint8_t config_id) const { + for (auto& config : configurations) { + if (config->bConfigurationValue == config_id) { + return config.get(); + } + } + return nullptr; + } + + int copyConfigDescriptor(const usbi_configuration_descriptor* config, + void* buf, + size_t buf_len) { + auto len = std::min(buf_len, (size_t)config->wTotalLength); + memcpy(buf, config, len); + return len; + } + + template + int awaitOnMain(const char* methodName, Args&&... args) const { + return ::awaitOnMain([&] { + return CaughtPromise(device.call( + methodName, std::forward(args)...)); + }) + .error; + } + + ~CachedDevice() { + runOnMain([device = std::move(device)] {}); + } + + CaughtPromise safeOpenCloseAssumingMainThread(OpenClose open) { + return val::take_ownership(usbi_em_device_safe_open_close( + device.as_handle(), static_cast(open))); + } + + int safeOpenCloseOnMain(OpenClose open) { + return ::awaitOnMain([this, open] { + return safeOpenCloseAssumingMainThread(open); + }) + .error; + } + +private: + + val device; + std::vector> configurations; + + CaughtPromise requestDescriptor(libusb_descriptor_type desc_type, + uint8_t desc_index, + uint16_t max_length) const { + libusb_control_setup setup = { + .bmRequestType = LIBUSB_ENDPOINT_IN, + .bRequest = LIBUSB_REQUEST_GET_DESCRIPTOR, + .wValue = (uint16_t)((desc_type << 8) | desc_index), + .wIndex = 0, + .wLength = max_length, + }; + return makeControlTransferPromise(device, &setup); + } + + // Implementation of the `CachedDevice::initFromDevice` above. This is a + // separate function just because we need to close the device on exit if + // we opened it successfully, and we can't use an async operation (`close`) + // in RAII destructor. + val initFromDeviceWithoutClosing(libusb_device* dev, bool& must_close) { + co_await_try(safeOpenCloseAssumingMainThread(OpenClose::Open)); + + // Can't use RAII to close on exit as co_await is not permitted in + // destructors (yet: + // https://github.com/cplusplus/papers/issues/445), so use a good + // old boolean + a wrapper instead. + must_close = true; + + { + auto result = co_await_try( + requestDescriptor(LIBUSB_DT_DEVICE, 0, LIBUSB_DT_DEVICE_SIZE)); + if (auto error = getTransferStatus(result)) { + co_return error; + } + copyFromDataView(&dev->device_descriptor, result["data"]); + } + + // Infer the device speed (which is not yet provided by WebUSB) from + // the descriptor. + if (dev->device_descriptor.bMaxPacketSize0 == + /* actually means 2^9, only valid for superspeeds */ 9) { + dev->speed = dev->device_descriptor.bcdUSB >= 0x0310 + ? LIBUSB_SPEED_SUPER_PLUS + : LIBUSB_SPEED_SUPER; + } else if (dev->device_descriptor.bcdUSB >= 0x0200) { + dev->speed = LIBUSB_SPEED_HIGH; + } else if (dev->device_descriptor.bMaxPacketSize0 > 8) { + dev->speed = LIBUSB_SPEED_FULL; + } else { + dev->speed = LIBUSB_SPEED_LOW; + } + + if (auto error = usbi_sanitize_device(dev)) { + co_return error; + } + + auto configurations_len = dev->device_descriptor.bNumConfigurations; + configurations.reserve(configurations_len); + for (uint8_t j = 0; j < configurations_len; j++) { + // Note: requesting more than (platform-specific limit) bytes + // here will cause the transfer to fail, see + // https://crbug.com/1489414. Use the most common limit of 4096 + // bytes for now. + constexpr uint16_t MAX_CTRL_BUFFER_LENGTH = 4096; + auto result = co_await_try( + requestDescriptor(LIBUSB_DT_CONFIG, j, MAX_CTRL_BUFFER_LENGTH)); + if (auto error = getTransferStatus(result)) { + co_return error; + } + auto configVal = result["data"]; + auto configLen = configVal["byteLength"].as(); + auto& config = configurations.emplace_back( + (usbi_configuration_descriptor*)::operator new(configLen)); + copyFromDataView(config.get(), configVal); + } -// Store the global `navigator.usb` once upon initialisation. -thread_local const val web_usb = val::global("navigator")["usb"]; + co_return (int) LIBUSB_SUCCESS; + } -enum StringId : uint8_t { - Manufacturer = 1, - Product = 2, - SerialNumber = 3, + CachedDevice(val device) : device(std::move(device)) {} + + friend struct ValPtr; }; -int em_get_device_list(libusb_context *ctx, discovered_devs **devs) { - // C++ equivalent of `await navigator.usb.getDevices()`. - // Note: at this point we must already have some devices exposed - - // caller must have called `await navigator.usb.requestDevice(...)` - // in response to user interaction before going to LibUSB. - // Otherwise this list will be empty. - auto result = promise_result::await(web_usb.call("getDevices")); - if (result.error) { - return result.error; - } - auto &web_usb_devices = result.value; - // Iterate over the exposed devices. - uint8_t devices_num = web_usb_devices["length"].as(); - for (uint8_t i = 0; i < devices_num; i++) { - auto web_usb_device = web_usb_devices[i]; - auto vendor_id = web_usb_device["vendorId"].as(); - auto product_id = web_usb_device["productId"].as(); - // TODO: this has to be a unique ID for the device in libusb structs. - // We can't really rely on the index in the list, and otherwise - // I can't think of a good way to assign permanent IDs to those - // devices, so here goes best-effort attempt... - unsigned long session_id = (vendor_id << 16) | product_id; - // LibUSB uses that ID to check if this device is already in its own - // list. As long as there are no two instances of same device - // connected and exposed to the page, we should be fine... - auto dev = usbi_get_device_by_session_id(ctx, session_id); - if (dev == NULL) { - dev = usbi_alloc_device(ctx, session_id); - if (dev == NULL) { - usbi_err(ctx, "failed to allocate a new device structure"); - continue; - } - - dev->device_descriptor = { - .bLength = LIBUSB_DT_DEVICE_SIZE, - .bDescriptorType = LIBUSB_DT_DEVICE, - .bcdUSB = static_cast( - (web_usb_device["usbVersionMajor"].as() << 8) | - (web_usb_device["usbVersionMinor"].as() << 4) | - web_usb_device["usbVersionSubminor"].as()), - .bDeviceClass = web_usb_device["deviceClass"].as(), - .bDeviceSubClass = web_usb_device["deviceSubclass"].as(), - .bDeviceProtocol = web_usb_device["deviceProtocol"].as(), - .bMaxPacketSize0 = 64, // yolo - .idVendor = vendor_id, - .idProduct = product_id, - .bcdDevice = static_cast( - (web_usb_device["deviceVersionMajor"].as() << 8) | - (web_usb_device["deviceVersionMinor"].as() << 4) | - web_usb_device["deviceVersionSubminor"].as()), - // Those are supposed to be indices for USB string descriptors. - // Normally they're part of the raw USB descriptor structure, but in - // our case we don't have it. Luckily, libusb provides hooks for that - // (to accomodate for other systems in similar position) so we can - // just assign constant IDs we can recognise later and then handle - // them in `em_submit_transfer` when there is a request to get string - // descriptor value. - .iManufacturer = StringId::Manufacturer, - .iProduct = StringId::Product, - .iSerialNumber = StringId::SerialNumber, - .bNumConfigurations = - web_usb_device["configurations"]["length"].as(), - }; - - if (usbi_sanitize_device(dev) < 0) { - libusb_unref_device(dev); - continue; - } - - WebUsbDevicePtr(dev).init_to(std::move(web_usb_device)); - } - *devs = discovered_devs_append(*devs, dev); - } - return LIBUSB_SUCCESS; -} +unsigned long getDeviceSessionId(val& web_usb_device) { + thread_local const val SessionIdSymbol = + val::global("Symbol")(val("libusb.session_id")); + + val session_id_val = web_usb_device[SessionIdSymbol]; + if (!session_id_val.isUndefined()) { + return session_id_val.as(); + } + + // If the device doesn't have a session ID, it means we haven't seen + // it before. Generate a new session ID for it. We can associate an + // incrementing ID with the `USBDevice` object itself. It's + // guaranteed to be alive and, thus, stable as long as the device is + // connected, even between different libusb invocations. See + // https://github.com/WICG/webusb/issues/241. + + static unsigned long next_session_id = 0; -int em_open(libusb_device_handle *handle) { - auto web_usb_device = get_web_usb_device(handle->dev); - return promise_result::await(web_usb_device.call("open")).error; + web_usb_device.set(SessionIdSymbol, next_session_id); + return next_session_id++; } -void em_close(libusb_device_handle *handle) { - // LibUSB API doesn't allow us to handle an error here, so ignore the Promise - // altogether. - return get_web_usb_device(handle->dev).call("close"); +val getDeviceList(libusb_context* ctx, discovered_devs** devs) { + // C++ equivalent of `await navigator.usb.getDevices()`. Note: at this point + // we must already have some devices exposed - caller must have called + // `await navigator.usb.requestDevice(...)` in response to user interaction + // before going to LibUSB. Otherwise this list will be empty. + auto web_usb_devices = + co_await_try(val::global("navigator")["usb"].call("getDevices")); + for (auto&& web_usb_device : web_usb_devices) { + auto session_id = getDeviceSessionId(web_usb_device); + + auto dev = usbi_get_device_by_session_id(ctx, session_id); + if (dev == NULL) { + dev = usbi_alloc_device(ctx, session_id); + if (dev == NULL) { + usbi_err(ctx, "failed to allocate a new device structure"); + continue; + } + + auto statusVal = co_await CachedDevice::initFromDevice( + std::move(web_usb_device), dev); + if (auto error = statusVal.as()) { + usbi_err(ctx, "failed to read device information: %s", + libusb_error_name(error)); + libusb_unref_device(dev); + continue; + } + + // We don't have real buses in WebUSB, just pretend everything + // is on bus 1. + dev->bus_number = 1; + // This can wrap around but it's the best approximation of a stable + // device address and port number we can provide. + dev->device_address = dev->port_number = (uint8_t)session_id; + } + *devs = discovered_devs_append(*devs, dev); + libusb_unref_device(dev); + } + co_return (int) LIBUSB_SUCCESS; } -int em_get_config_descriptor_impl(val &&web_usb_config, void *buf, size_t len) { - const auto buf_start = static_cast(buf); - auto web_usb_interfaces = web_usb_config["interfaces"]; - auto num_interfaces = web_usb_interfaces["length"].as(); - auto config = static_cast(buf); - *config = { - .bLength = LIBUSB_DT_CONFIG_SIZE, - .bDescriptorType = LIBUSB_DT_CONFIG, - .wTotalLength = LIBUSB_DT_CONFIG_SIZE, - .bNumInterfaces = num_interfaces, - .bConfigurationValue = web_usb_config["configurationValue"].as(), - .iConfiguration = - 0, // TODO: assign some index and handle `configurationName` - .bmAttributes = - 1 << 7, // bus powered (should be always set according to docs) - .bMaxPower = 0, // yolo - }; - buf = static_cast(buf) + LIBUSB_DT_CONFIG_SIZE; - for (uint8_t i = 0; i < num_interfaces; i++) { - auto web_usb_interface = web_usb_interfaces[i]; - // TODO: update to `web_usb_interface["alternate"]` once - // fix for https://bugs.chromium.org/p/chromium/issues/detail?id=1093502 is - // stable. - auto web_usb_alternate = web_usb_interface["alternates"][0]; - auto web_usb_endpoints = web_usb_alternate["endpoints"]; - auto num_endpoints = web_usb_endpoints["length"].as(); - config->wTotalLength += - LIBUSB_DT_INTERFACE_SIZE + num_endpoints * LIBUSB_DT_ENDPOINT_SIZE; - if (config->wTotalLength > len) { - continue; - } - auto interface = static_cast(buf); - *interface = { - .bLength = LIBUSB_DT_INTERFACE_SIZE, - .bDescriptorType = LIBUSB_DT_INTERFACE, - .bInterfaceNumber = web_usb_interface["interfaceNumber"].as(), - .bAlternateSetting = - web_usb_alternate["alternateSetting"].as(), - .bNumEndpoints = web_usb_endpoints["length"].as(), - .bInterfaceClass = web_usb_alternate["interfaceClass"].as(), - .bInterfaceSubClass = - web_usb_alternate["interfaceSubclass"].as(), - .bInterfaceProtocol = - web_usb_alternate["interfaceProtocol"].as(), - .iInterface = 0, // Not exposed in WebUSB, don't assign any string. - }; - buf = static_cast(buf) + LIBUSB_DT_INTERFACE_SIZE; - for (uint8_t j = 0; j < num_endpoints; j++) { - auto web_usb_endpoint = web_usb_endpoints[j]; - auto endpoint = static_cast(buf); - - auto web_usb_endpoint_type = web_usb_endpoint["type"].as(); - auto transfer_type = LIBUSB_ENDPOINT_TRANSFER_TYPE_CONTROL; - - if (web_usb_endpoint_type == "bulk") { - transfer_type = LIBUSB_ENDPOINT_TRANSFER_TYPE_BULK; - } else if (web_usb_endpoint_type == "interrupt") { - transfer_type = LIBUSB_ENDPOINT_TRANSFER_TYPE_INTERRUPT; - } else if (web_usb_endpoint_type == "isochronous") { - transfer_type = LIBUSB_ENDPOINT_TRANSFER_TYPE_ISOCHRONOUS; - } - - // Can't use struct-init syntax here because there is no - // `usbi_endpoint_descriptor` unlike for other descriptors, so we use - // `libusb_endpoint_descriptor` instead which has extra libusb-specific - // fields and might overflow the provided buffer. - endpoint->bLength = LIBUSB_DT_ENDPOINT_SIZE; - endpoint->bDescriptorType = LIBUSB_DT_ENDPOINT; - endpoint->bEndpointAddress = - ((web_usb_endpoint["direction"].as() == "in") << 7) | - web_usb_endpoint["endpointNumber"].as(); - endpoint->bmAttributes = transfer_type; - endpoint->wMaxPacketSize = web_usb_endpoint["packetSize"].as(); - endpoint->bInterval = 1; - - buf = static_cast(buf) + LIBUSB_DT_ENDPOINT_SIZE; - } - } - return static_cast(buf) - buf_start; +int em_get_device_list(libusb_context* ctx, discovered_devs** devs) { + // No need to wrap into CaughtPromise as we catch all individual ops in the + // inner implementation and return just the error code. We do need a custom + // promise type to ensure conversion to int happens on the main thread + // though. + struct IntPromise : val { + IntPromise(val&& promise) : val(std::move(promise)) {} + + struct AwaitResult { + int error; + + AwaitResult(val&& result) : error(result.as()) {} + }; + }; + + return awaitOnMain( + [ctx, devs] { return IntPromise(getDeviceList(ctx, devs)); }) + .error; } -int em_get_active_config_descriptor(libusb_device *dev, void *buf, size_t len) { - auto web_usb_config = get_web_usb_device(dev)["configuration"]; - if (web_usb_config.isNull()) { - return LIBUSB_ERROR_NOT_FOUND; - } - return em_get_config_descriptor_impl(std::move(web_usb_config), buf, len); +int em_open(libusb_device_handle* handle) { + return WebUsbDevicePtr(handle)->safeOpenCloseOnMain(OpenClose::Open); } -int em_get_config_descriptor(libusb_device *dev, uint8_t idx, void *buf, - size_t len) { - return em_get_config_descriptor_impl( - get_web_usb_device(dev)["configurations"][idx], buf, len); +void em_close(libusb_device_handle* handle) { + // LibUSB API doesn't allow us to handle an error here, but we still need to + // wait for the promise to make sure that subsequent attempt to reopen the + // same device doesn't fail with a "device busy" error. + if (auto error = + WebUsbDevicePtr(handle)->safeOpenCloseOnMain(OpenClose::Close)) { + usbi_err(handle->dev->ctx, "failed to close device: %s", + libusb_error_name(error)); + } } -int em_get_configuration(libusb_device_handle *dev_handle, uint8_t *config) { - auto web_usb_config = get_web_usb_device(dev_handle->dev)["configuration"]; - if (!web_usb_config.isNull()) { - *config = web_usb_config["configurationValue"].as(); - } - return LIBUSB_SUCCESS; +int em_get_active_config_descriptor(libusb_device* dev, void* buf, size_t len) { + auto& cached_device = *WebUsbDevicePtr(dev); + auto config_value = cached_device.getActiveConfigValue(); + if (auto config = cached_device.findConfigDescriptorByValue(config_value)) { + return cached_device.copyConfigDescriptor(config, buf, len); + } else { + return LIBUSB_ERROR_NOT_FOUND; + } } -int em_set_configuration(libusb_device_handle *handle, int config) { - return promise_result::await(get_web_usb_device(handle->dev) - .call("selectConfiguration", config)) - .error; +int em_get_config_descriptor(libusb_device* dev, + uint8_t config_id, + void* buf, + size_t len) { + auto& cached_device = *WebUsbDevicePtr(dev); + if (auto config = cached_device.getConfigDescriptor(config_id)) { + return cached_device.copyConfigDescriptor(config, buf, len); + } else { + return LIBUSB_ERROR_NOT_FOUND; + } } -int em_claim_interface(libusb_device_handle *handle, uint8_t iface) { - return promise_result::await( - get_web_usb_device(handle->dev).call("claimInterface", iface)) - .error; +int em_get_configuration(libusb_device_handle* dev_handle, + uint8_t* config_value) { + *config_value = WebUsbDevicePtr(dev_handle)->getActiveConfigValue(); + return LIBUSB_SUCCESS; } -int em_release_interface(libusb_device_handle *handle, uint8_t iface) { - return promise_result::await(get_web_usb_device(handle->dev) - .call("releaseInterface", iface)) - .error; +int em_get_config_descriptor_by_value(libusb_device* dev, + uint8_t config_value, + void** buf) { + auto& cached_device = *WebUsbDevicePtr(dev); + if (auto config = cached_device.findConfigDescriptorByValue(config_value)) { + *buf = config; + return config->wTotalLength; + } else { + return LIBUSB_ERROR_NOT_FOUND; + } } -int em_set_interface_altsetting(libusb_device_handle *handle, uint8_t iface, - uint8_t altsetting) { - return promise_result::await( - get_web_usb_device(handle->dev) - .call("selectAlternateInterface", iface, altsetting)) - .error; +int em_set_configuration(libusb_device_handle* dev_handle, int config) { + return WebUsbDevicePtr(dev_handle)->awaitOnMain("setConfiguration", config); } -int em_clear_halt(libusb_device_handle *handle, unsigned char endpoint) { - std::string direction = endpoint & LIBUSB_ENDPOINT_IN ? "in" : "out"; - endpoint &= LIBUSB_ENDPOINT_ADDRESS_MASK; +int em_claim_interface(libusb_device_handle* handle, uint8_t iface) { + return WebUsbDevicePtr(handle)->awaitOnMain("claimInterface", iface); +} - return promise_result::await(get_web_usb_device(handle->dev) - .call("clearHalt", direction, endpoint)) - .error; +int em_release_interface(libusb_device_handle* handle, uint8_t iface) { + return WebUsbDevicePtr(handle)->awaitOnMain("releaseInterface", iface); } -int em_reset_device(libusb_device_handle *handle) { - return promise_result::await( - get_web_usb_device(handle->dev).call("reset")) - .error; +int em_set_interface_altsetting(libusb_device_handle* handle, + uint8_t iface, + uint8_t altsetting) { + return WebUsbDevicePtr(handle)->awaitOnMain("selectAlternateInterface", + iface, altsetting); } -void em_destroy_device(libusb_device *dev) { WebUsbDevicePtr(dev).take(); } +int em_clear_halt(libusb_device_handle* handle, unsigned char endpoint) { + std::string direction = endpoint & LIBUSB_ENDPOINT_IN ? "in" : "out"; + endpoint &= LIBUSB_ENDPOINT_ADDRESS_MASK; -thread_local const val Uint8Array = val::global("Uint8Array"); + return WebUsbDevicePtr(handle)->awaitOnMain("clearHalt", direction, + endpoint); +} -EMSCRIPTEN_KEEPALIVE -extern "C" void em_signal_transfer_completion(usbi_transfer *itransfer, - EM_VAL result_handle) { - em_signal_transfer_completion_impl(itransfer, - val::take_ownership(result_handle)); +int em_reset_device(libusb_device_handle* handle) { + return WebUsbDevicePtr(handle)->awaitOnMain("reset"); } -// clang-format off -EM_JS(void, em_start_transfer_impl, (usbi_transfer *transfer, EM_VAL handle), { - // Right now the handle value should be a `Promise<{value, error}>`. - // Subscribe to its result to unwrap the promise to `{value, error}` - // and signal transfer completion. - // Catch the error to transform promise of `value` into promise of `{value, - // error}`. - Emval.toValue(handle).then(result => { - _em_signal_transfer_completion(transfer, Emval.toHandle(result)); - }); -}); -// clang-format on +void em_destroy_device(libusb_device* dev) { + WebUsbDevicePtr(dev).free(); +} + +int em_submit_transfer(usbi_transfer* itransfer) { + return runOnMain([itransfer] { + auto transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + auto& web_usb_device = WebUsbDevicePtr(transfer->dev_handle) + ->getDeviceAssumingMainThread(); + val transfer_promise; + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_CONTROL: { + transfer_promise = makeControlTransferPromise( + web_usb_device, + libusb_control_transfer_get_setup(transfer)); + break; + } + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_INTERRUPT: { + auto endpoint = + transfer->endpoint & LIBUSB_ENDPOINT_ADDRESS_MASK; + + if (IS_XFERIN(transfer)) { + transfer_promise = web_usb_device.call( + "transferIn", endpoint, transfer->length); + } else { + auto data = getUnsharedMemoryView(transfer->buffer, + transfer->length); + transfer_promise = + web_usb_device.call("transferOut", endpoint, data); + } -void em_start_transfer(usbi_transfer *itransfer, val &&promise) { - promise = em_promise_catch(std::move(promise)); - em_start_transfer_impl(itransfer, promise.as_handle()); + break; + } + // TODO: add implementation for isochronous transfers too. + default: + return LIBUSB_ERROR_NOT_SUPPORTED; + } + // Not a coroutine because we don't want to block on this promise, just + // schedule an asynchronous callback. + promiseThen(CaughtPromise(std::move(transfer_promise)), + [itransfer](auto&& result) { + WebUsbTransferPtr(itransfer).emplace(std::move(result)); + usbi_signal_transfer_completion(itransfer); + }); + return LIBUSB_SUCCESS; + }); } -int em_submit_transfer(usbi_transfer *itransfer) { - auto transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); - auto web_usb_device = get_web_usb_device(transfer->dev_handle->dev); - switch (transfer->type) { - case LIBUSB_TRANSFER_TYPE_CONTROL: { - auto setup = libusb_control_transfer_get_setup(transfer); - auto web_usb_control_transfer_params = val::object(); - - const char *web_usb_request_type = "unknown"; - // See LIBUSB_REQ_TYPE in windows_winusb.h (or docs for `bmRequestType`). - switch (setup->bmRequestType & (0x03 << 5)) { - case LIBUSB_REQUEST_TYPE_STANDARD: - if (setup->bRequest == LIBUSB_REQUEST_GET_DESCRIPTOR && - setup->wValue >> 8 == LIBUSB_DT_STRING) { - // For string descriptors we provide custom implementation that - // doesn't require an actual transfer, but just retrieves the value - // from JS, stores that string handle as transfer data (instead of a - // Promise) and immediately signals completion. - const char *propName = nullptr; - switch (setup->wValue & 0xFF) { - case StringId::Manufacturer: - propName = "manufacturerName"; - break; - case StringId::Product: - propName = "productName"; - break; - case StringId::SerialNumber: - propName = "serialNumber"; - break; - } - if (propName != nullptr) { - val str = web_usb_device[propName]; - if (str.isNull()) { - str = val(""); - } - em_signal_transfer_completion_impl(itransfer, std::move(str)); - return LIBUSB_SUCCESS; - } - } - web_usb_request_type = "standard"; - break; - case LIBUSB_REQUEST_TYPE_CLASS: - web_usb_request_type = "class"; - break; - case LIBUSB_REQUEST_TYPE_VENDOR: - web_usb_request_type = "vendor"; - break; - } - web_usb_control_transfer_params.set("requestType", web_usb_request_type); - - const char *recipient = "other"; - switch (setup->bmRequestType & 0x0f) { - case LIBUSB_RECIPIENT_DEVICE: - recipient = "device"; - break; - case LIBUSB_RECIPIENT_INTERFACE: - recipient = "interface"; - break; - case LIBUSB_RECIPIENT_ENDPOINT: - recipient = "endpoint"; - break; - } - web_usb_control_transfer_params.set("recipient", recipient); - - web_usb_control_transfer_params.set("request", setup->bRequest); - web_usb_control_transfer_params.set("value", setup->wValue); - web_usb_control_transfer_params.set("index", setup->wIndex); - - if (setup->bmRequestType & LIBUSB_ENDPOINT_IN) { - em_start_transfer( - itransfer, - web_usb_device.call("controlTransferIn", - std::move(web_usb_control_transfer_params), - setup->wLength)); - } else { - auto data = - val(typed_memory_view(setup->wLength, - libusb_control_transfer_get_data(transfer))) - .call("slice"); - em_start_transfer( - itransfer, web_usb_device.call( - "controlTransferOut", - std::move(web_usb_control_transfer_params), data)); - } - - break; - } - case LIBUSB_TRANSFER_TYPE_BULK: - case LIBUSB_TRANSFER_TYPE_INTERRUPT: { - auto endpoint = transfer->endpoint & LIBUSB_ENDPOINT_ADDRESS_MASK; - - if (IS_XFERIN(transfer)) { - em_start_transfer( - itransfer, - web_usb_device.call("transferIn", endpoint, transfer->length)); - } else { - auto data = val(typed_memory_view(transfer->length, transfer->buffer)) - .call("slice"); - em_start_transfer( - itransfer, web_usb_device.call("transferOut", endpoint, data)); - } - - break; - } - // TODO: add implementation for isochronous transfers too. - default: - return LIBUSB_ERROR_NOT_SUPPORTED; - } - return LIBUSB_SUCCESS; +void em_clear_transfer_priv(usbi_transfer* itransfer) { + WebUsbTransferPtr(itransfer).free(); } -void em_clear_transfer_priv(usbi_transfer *itransfer) { - WebUsbTransferPtr(itransfer).take(); +int em_cancel_transfer(usbi_transfer* itransfer) { + return LIBUSB_SUCCESS; } -int em_cancel_transfer(usbi_transfer *itransfer) { return LIBUSB_SUCCESS; } - -int em_handle_transfer_completion(usbi_transfer *itransfer) { - auto transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); - - // Take ownership of the transfer result, as `em_clear_transfer_priv` - // is not called automatically for completed transfers and we must - // free it to avoid leaks. - - auto result_val = WebUsbTransferPtr(itransfer).take(); - - if (itransfer->state_flags & USBI_TRANSFER_CANCELLING) { - return usbi_handle_transfer_cancellation(itransfer); - } - - libusb_transfer_status status = LIBUSB_TRANSFER_ERROR; - - // If this was a LIBUSB_DT_STRING request, then the value will be a string - // handle instead of a promise. - if (result_val.isString()) { - int written = EM_ASM_INT( - { - // There's no good way to get UTF-16 output directly from JS string, - // so again reach out to internals via JS snippet. - return stringToUTF16(Emval.toValue($0), $1, $2); - }, - result_val.as_handle(), - transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE + 2, - transfer->length - LIBUSB_CONTROL_SETUP_SIZE - 2); - itransfer->transferred = transfer->buffer[LIBUSB_CONTROL_SETUP_SIZE] = - 2 + written; - transfer->buffer[LIBUSB_CONTROL_SETUP_SIZE + 1] = LIBUSB_DT_STRING; - status = LIBUSB_TRANSFER_COMPLETED; - } else { - // Otherwise we should have a `{value, error}` object by now (see - // `em_start_transfer_impl` callback). - promise_result result(std::move(result_val)); - - if (!result.error) { - auto web_usb_transfer_status = result.value["status"].as(); - if (web_usb_transfer_status == "ok") { - status = LIBUSB_TRANSFER_COMPLETED; - } else if (web_usb_transfer_status == "stall") { - status = LIBUSB_TRANSFER_STALL; - } else if (web_usb_transfer_status == "babble") { - status = LIBUSB_TRANSFER_OVERFLOW; - } - - int skip; - unsigned char endpointDir; - - if (transfer->type == LIBUSB_TRANSFER_TYPE_CONTROL) { - skip = LIBUSB_CONTROL_SETUP_SIZE; - endpointDir = - libusb_control_transfer_get_setup(transfer)->bmRequestType; - } else { - skip = 0; - endpointDir = transfer->endpoint; - } - - if (endpointDir & LIBUSB_ENDPOINT_IN) { - auto data = result.value["data"]; - if (!data.isNull()) { - itransfer->transferred = data["byteLength"].as(); - val(typed_memory_view(transfer->length - skip, - transfer->buffer + skip)) - .call("set", Uint8Array.new_(data["buffer"])); - } - } else { - itransfer->transferred = result.value["bytesWritten"].as(); - } - } - } - - return usbi_handle_transfer_completion(itransfer, status); +int em_handle_transfer_completion(usbi_transfer* itransfer) { + libusb_transfer_status status = runOnMain([itransfer] { + auto transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + // Take ownership of the transfer result, as `em_clear_transfer_priv` is + // not called automatically for completed transfers and we must free it + // to avoid leaks. + + auto result = WebUsbTransferPtr(itransfer).take(); + + if (itransfer->state_flags & USBI_TRANSFER_CANCELLING) { + return LIBUSB_TRANSFER_CANCELLED; + } + + if (result.error) { + return LIBUSB_TRANSFER_ERROR; + } + + auto& value = result.value; + + void* dataDest; + unsigned char endpointDir; + + if (transfer->type == LIBUSB_TRANSFER_TYPE_CONTROL) { + dataDest = libusb_control_transfer_get_data(transfer); + endpointDir = + libusb_control_transfer_get_setup(transfer)->bmRequestType; + } else { + dataDest = transfer->buffer; + endpointDir = transfer->endpoint; + } + + if (endpointDir & LIBUSB_ENDPOINT_IN) { + auto data = value["data"]; + if (!data.isNull()) { + itransfer->transferred = data["byteLength"].as(); + copyFromDataView(dataDest, data); + } + } else { + itransfer->transferred = value["bytesWritten"].as(); + } + + return getTransferStatus(value); + }); + + // Invoke user's handlers outside of the main thread to reduce pressure. + return status == LIBUSB_TRANSFER_CANCELLED + ? usbi_handle_transfer_cancellation(itransfer) + : usbi_handle_transfer_completion(itransfer, status); } + } // namespace -extern "C" { -const usbi_os_backend usbi_backend = { - .name = "Emscripten + WebUSB backend", - .caps = LIBUSB_CAP_HAS_CAPABILITY, - .get_device_list = em_get_device_list, - .open = em_open, - .close = em_close, - .get_active_config_descriptor = em_get_active_config_descriptor, - .get_config_descriptor = em_get_config_descriptor, - .get_configuration = em_get_configuration, - .set_configuration = em_set_configuration, - .claim_interface = em_claim_interface, - .release_interface = em_release_interface, - .set_interface_altsetting = em_set_interface_altsetting, - .clear_halt = em_clear_halt, - .reset_device = em_reset_device, - .destroy_device = em_destroy_device, - .submit_transfer = em_submit_transfer, - .cancel_transfer = em_cancel_transfer, - .clear_transfer_priv = em_clear_transfer_priv, - .handle_transfer_completion = em_handle_transfer_completion, - .device_priv_size = sizeof(val), - .transfer_priv_size = sizeof(val), +#pragma clang diagnostic ignored "-Wmissing-field-initializers" +extern "C" const usbi_os_backend usbi_backend = { + .name = "Emscripten + WebUSB backend", + .caps = LIBUSB_CAP_HAS_CAPABILITY, + .get_device_list = em_get_device_list, + .open = em_open, + .close = em_close, + .get_active_config_descriptor = em_get_active_config_descriptor, + .get_config_descriptor = em_get_config_descriptor, + .get_config_descriptor_by_value = em_get_config_descriptor_by_value, + .get_configuration = em_get_configuration, + .set_configuration = em_set_configuration, + .claim_interface = em_claim_interface, + .release_interface = em_release_interface, + .set_interface_altsetting = em_set_interface_altsetting, + .clear_halt = em_clear_halt, + .reset_device = em_reset_device, + .destroy_device = em_destroy_device, + .submit_transfer = em_submit_transfer, + .cancel_transfer = em_cancel_transfer, + .clear_transfer_priv = em_clear_transfer_priv, + .handle_transfer_completion = em_handle_transfer_completion, + .device_priv_size = sizeof(CachedDevice), + .transfer_priv_size = sizeof(PromiseResult), }; -} + #pragma clang diagnostic pop diff --git a/libusb/libusb/os/events_posix.c b/libusb/libusb/os/events_posix.c index 2ba0103..4056dae 100644 --- a/libusb/libusb/os/events_posix.c +++ b/libusb/libusb/os/events_posix.c @@ -37,26 +37,21 @@ * * Therefore use a custom event system based on browser event emitters. */ #include +#include +#include -EM_JS(void, em_libusb_notify, (void), { - dispatchEvent(new Event("em-libusb")); +EM_ASYNC_JS(void, em_libusb_wait_async, (const _Atomic int* ptr, int expected_value, int timeout), { + await Atomics.waitAsync(HEAP32, ptr >> 2, expected_value, timeout).value; }); -EM_ASYNC_JS(int, em_libusb_wait, (int timeout), { - let onEvent, timeoutId; - - try { - return await new Promise(resolve => { - onEvent = () => resolve(0); - addEventListener('em-libusb', onEvent); - - timeoutId = setTimeout(resolve, timeout, -1); - }); - } finally { - removeEventListener('em-libusb', onEvent); - clearTimeout(timeoutId); +static void em_libusb_wait(const _Atomic int *ptr, int expected_value, int timeout) +{ + if (emscripten_is_main_runtime_thread()) { + em_libusb_wait_async(ptr, expected_value, timeout); + } else { + emscripten_atomic_wait_u32((int*)ptr, expected_value, 1000000LL * timeout); } -}); +} #endif #include @@ -162,7 +157,8 @@ void usbi_signal_event(usbi_event_t *event) if (r != sizeof(dummy)) usbi_warn(NULL, "event write failed"); #ifdef __EMSCRIPTEN__ - em_libusb_notify(); + event->has_event = 1; + emscripten_atomic_notify(&event->has_event, EMSCRIPTEN_NOTIFY_ALL_WAITERS); #endif } @@ -174,6 +170,9 @@ void usbi_clear_event(usbi_event_t *event) r = read(EVENT_READ_FD(event), &dummy, sizeof(dummy)); if (r != sizeof(dummy)) usbi_warn(NULL, "event read failed"); +#ifdef __EMSCRIPTEN__ + event->has_event = 0; +#endif } #ifdef HAVE_TIMERFD @@ -257,22 +256,14 @@ int usbi_wait_for_events(struct libusb_context *ctx, usbi_dbg(ctx, "poll() %u fds with timeout in %dms", (unsigned int)nfds, timeout_ms); #ifdef __EMSCRIPTEN__ - /* TODO: improve event system to watch only for fd events we're interested in - * (although a scenario where we have multiple watchers in parallel is very rare - * in real world anyway). */ - double until_time = emscripten_get_now() + timeout_ms; - for (;;) { - /* Emscripten `poll` ignores timeout param, but pass 0 explicitly just in case. */ - num_ready = poll(fds, nfds, 0); - if (num_ready != 0) break; - int timeout = until_time - emscripten_get_now(); - if (timeout <= 0) break; - int result = em_libusb_wait(timeout); - if (result != 0) break; - } -#else - num_ready = poll(fds, nfds, timeout_ms); + // Emscripten's poll doesn't actually block, so we need to use an out-of-band + // waiting signal. + em_libusb_wait(&ctx->event.has_event, 0, timeout_ms); + // Emscripten ignores timeout_ms, but set it to 0 for future-proofing in case + // they ever implement real poll. + timeout_ms = 0; #endif + num_ready = poll(fds, nfds, timeout_ms); usbi_dbg(ctx, "poll() returned %d", num_ready); if (num_ready == 0) { if (usbi_using_timer(ctx)) diff --git a/libusb/libusb/os/events_posix.h b/libusb/libusb/os/events_posix.h index d81b5c4..4bd7f0f 100644 --- a/libusb/libusb/os/events_posix.h +++ b/libusb/libusb/os/events_posix.h @@ -36,6 +36,9 @@ typedef struct usbi_event { #else typedef struct usbi_event { int pipefd[2]; +#ifdef __EMSCRIPTEN__ + _Atomic int has_event; +#endif } usbi_event_t; #define USBI_EVENT_OS_HANDLE(e) ((e)->pipefd[0]) #define USBI_EVENT_POLL_EVENTS POLLIN diff --git a/libusb/libusb/os/linux_usbfs.c b/libusb/libusb/os/linux_usbfs.c index 1115a04..ed8597b 100644 --- a/libusb/libusb/os/linux_usbfs.c +++ b/libusb/libusb/os/linux_usbfs.c @@ -652,7 +652,7 @@ static int seek_to_next_config(struct libusb_context *ctx, while (len > 0) { if (len < 2) { - usbi_err(ctx, "short descriptor read %zu/2", len); + usbi_err(ctx, "remaining descriptor length too small %zu/2", len); return LIBUSB_ERROR_IO; } @@ -660,6 +660,11 @@ static int seek_to_next_config(struct libusb_context *ctx, if (header->bDescriptorType == LIBUSB_DT_CONFIG) return offset; + if (header->bLength < 2) { + usbi_err(ctx, "invalid descriptor bLength %hhu", header->bLength); + return LIBUSB_ERROR_IO; + } + if (len < header->bLength) { usbi_err(ctx, "bLength overflow by %zu bytes", (size_t)header->bLength - len); diff --git a/libusb/libusb/os/sunos_usb.c b/libusb/libusb/os/sunos_usb.c index 9c08f7e..6c8250c 100644 --- a/libusb/libusb/os/sunos_usb.c +++ b/libusb/libusb/os/sunos_usb.c @@ -82,7 +82,7 @@ static int sunos_usb_ioctl(struct libusb_device *dev, int cmd); static int sunos_get_link(di_devlink_t devlink, void *arg) { - walk_link_t *larg = (walk_link_t *)arg; + walk_link_t *link_arg = (walk_link_t *)arg; const char *p; const char *q; @@ -112,21 +112,21 @@ static int sunos_get_link(di_devlink_t devlink, void *arg) static int sunos_physpath_to_devlink( const char *node_path, const char *match, char **link_path) { - walk_link_t larg; + walk_link_t link_arg; di_devlink_handle_t hdl; *link_path = NULL; - larg.linkpp = link_path; + link_arg.linkpp = link_path; if ((hdl = di_devlink_init(NULL, 0)) == NULL) { usbi_dbg(NULL, "di_devlink_init failure"); return (-1); } - larg.len = strlen(node_path); - larg.path = (char *)node_path; + link_arg.len = strlen(node_path); + link_arg.path = (char *)node_path; (void) di_devlink_walk(hdl, match, NULL, DI_PRIMARY_LINK, - (void *)&larg, sunos_get_link); + (void *)&link_arg, sunos_get_link); (void) di_devlink_fini(&hdl); @@ -624,7 +624,7 @@ sunos_add_devices(di_devlink_t link, void *arg) } if (usbi_sanitize_device(dev) < 0) { libusb_unref_device(dev); - usbi_dbg(NULL, "sanatize failed: "); + usbi_dbg(NULL, "sanitize failed: "); return (DI_WALK_TERMINATE); } } else { diff --git a/libusb/libusb/os/windows_common.c b/libusb/libusb/os/windows_common.c index 9054349..5373db9 100644 --- a/libusb/libusb/os/windows_common.c +++ b/libusb/libusb/os/windows_common.c @@ -491,9 +491,10 @@ static unsigned __stdcall windows_iocp_thread(void *arg) continue; } - itransfer = (struct usbi_transfer *)((unsigned char *)transfer_priv + PTR_ALIGN(sizeof(*transfer_priv))); + itransfer = TRANSFER_PRIV_TO_USBI_TRANSFER(transfer_priv); + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); usbi_dbg(ctx, "transfer %p completed, length %lu", - USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer), ULONG_CAST(num_bytes)); + transfer, ULONG_CAST(num_bytes)); usbi_signal_transfer_completion(itransfer); } @@ -608,6 +609,8 @@ static int windows_set_option(struct libusb_context *ctx, enum libusb_option opt { struct windows_context_priv *priv = usbi_get_context_priv(ctx); + UNUSED(ap); + if (option == LIBUSB_OPTION_USE_USBDK) { if (!usbdk_available) { usbi_err(ctx, "UsbDk backend not available"); @@ -618,10 +621,6 @@ static int windows_set_option(struct libusb_context *ctx, enum libusb_option opt return LIBUSB_SUCCESS; } - if (priv->backend->set_option) { - return priv->backend->set_option(ctx, option, ap); - } - return LIBUSB_ERROR_NOT_SUPPORTED; } @@ -807,8 +806,9 @@ static int windows_handle_transfer_completion(struct usbi_transfer *itransfer) else result = GetLastError(); + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); usbi_dbg(ctx, "handling transfer %p completion with errcode %lu, length %lu", - USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer), ULONG_CAST(result), ULONG_CAST(bytes_transferred)); + transfer, ULONG_CAST(result), ULONG_CAST(bytes_transferred)); switch (result) { case NO_ERROR: diff --git a/libusb/libusb/os/windows_common.h b/libusb/libusb/os/windows_common.h index cdee97f..0c0ff4b 100644 --- a/libusb/libusb/os/windows_common.h +++ b/libusb/libusb/os/windows_common.h @@ -342,7 +342,6 @@ struct windows_backend { int (*cancel_transfer)(struct usbi_transfer *itransfer); void (*clear_transfer_priv)(struct usbi_transfer *itransfer); enum libusb_transfer_status (*copy_transfer_data)(struct usbi_transfer *itransfer, DWORD length); - int (*set_option)(struct libusb_context *ctx, enum libusb_option option, va_list args); }; struct windows_context_priv { diff --git a/libusb/libusb/os/windows_usbdk.c b/libusb/libusb/os/windows_usbdk.c index 4c08bcf..9f52b48 100644 --- a/libusb/libusb/os/windows_usbdk.c +++ b/libusb/libusb/os/windows_usbdk.c @@ -721,5 +721,4 @@ const struct windows_backend usbdk_backend = { NULL, /* cancel_transfer */ usbdk_clear_transfer_priv, usbdk_copy_transfer_data, - NULL /* usbdk_set_option */, }; diff --git a/libusb/libusb/os/windows_winusb.c b/libusb/libusb/os/windows_winusb.c index 93f2a4e..926b9e8 100644 --- a/libusb/libusb/os/windows_winusb.c +++ b/libusb/libusb/os/windows_winusb.c @@ -42,8 +42,6 @@ continue; \ } -static int interface_by_endpoint(struct winusb_device_priv *priv, - struct winusb_device_handle_priv *handle_priv, uint8_t endpoint_address); // WinUSB-like API prototypes static bool winusbx_init(struct libusb_context *ctx); static void winusbx_exit(void); @@ -1074,7 +1072,6 @@ static int init_device(struct libusb_device *dev, struct libusb_device *parent_d DWORD size; uint8_t bus_number, depth; int r; - int ginfotimeout; priv = usbi_get_device_priv(dev); @@ -1135,61 +1132,45 @@ static int init_device(struct libusb_device *dev, struct libusb_device *parent_d conn_info.ConnectionIndex = (ULONG)port_number; // coverity[tainted_data_argument] - ginfotimeout = 20; - do { - if (!DeviceIoControl(hub_handle, IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX, &conn_info, sizeof(conn_info), - &conn_info, sizeof(conn_info), &size, NULL)) { - usbi_warn(ctx, "could not get node connection information for device '%s': %s", - priv->dev_id, windows_error_str(0)); - CloseHandle(hub_handle); - return LIBUSB_ERROR_NO_DEVICE; - } - - if (conn_info.ConnectionStatus == NoDeviceConnected) { - usbi_err(ctx, "device '%s' is no longer connected!", priv->dev_id); - CloseHandle(hub_handle); - return LIBUSB_ERROR_NO_DEVICE; - } - if ((conn_info.DeviceDescriptor.bLength != LIBUSB_DT_DEVICE_SIZE) - || (conn_info.DeviceDescriptor.bDescriptorType != LIBUSB_DT_DEVICE)) { - SleepEx(50, TRUE); - continue; - } + if (!DeviceIoControl(hub_handle, IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX, &conn_info, sizeof(conn_info), + &conn_info, sizeof(conn_info), &size, NULL)) { + usbi_warn(ctx, "could not get node connection information for device '%s': %s", + priv->dev_id, windows_error_str(0)); + CloseHandle(hub_handle); + return LIBUSB_ERROR_NO_DEVICE; + } - static_assert(sizeof(dev->device_descriptor) == sizeof(conn_info.DeviceDescriptor), - "mismatch between libusb and OS device descriptor sizes"); - memcpy(&dev->device_descriptor, &conn_info.DeviceDescriptor, LIBUSB_DT_DEVICE_SIZE); - usbi_localize_device_descriptor(&dev->device_descriptor); - - priv->active_config = conn_info.CurrentConfigurationValue; - if (priv->active_config == 0) { - usbi_dbg(ctx, "0x%x:0x%x found %u configurations (not configured)", - dev->device_descriptor.idVendor, - dev->device_descriptor.idProduct, - dev->device_descriptor.bNumConfigurations); - SleepEx(50, TRUE); - } - } while (priv->active_config == 0 && --ginfotimeout >= 0); + if (conn_info.ConnectionStatus == NoDeviceConnected) { + usbi_err(ctx, "device '%s' is no longer connected!", priv->dev_id); + CloseHandle(hub_handle); + return LIBUSB_ERROR_NO_DEVICE; + } if ((conn_info.DeviceDescriptor.bLength != LIBUSB_DT_DEVICE_SIZE) - || (conn_info.DeviceDescriptor.bDescriptorType != LIBUSB_DT_DEVICE)) { + || (conn_info.DeviceDescriptor.bDescriptorType != LIBUSB_DT_DEVICE)) { usbi_err(ctx, "device '%s' has invalid descriptor!", priv->dev_id); CloseHandle(hub_handle); return LIBUSB_ERROR_OTHER; } - if (priv->active_config == 0) { - usbi_info(ctx, "0x%x:0x%x found %u configurations but device isn't configured, " - "forcing current configuration to 1", - dev->device_descriptor.idVendor, - dev->device_descriptor.idProduct, - dev->device_descriptor.bNumConfigurations); - priv->active_config = 1; - } else { - usbi_dbg(ctx, "found %u configurations (current config: %u)", dev->device_descriptor.bNumConfigurations, priv->active_config); + static_assert(sizeof(dev->device_descriptor) == sizeof(conn_info.DeviceDescriptor), + "mismatch between libusb and OS device descriptor sizes"); + memcpy(&dev->device_descriptor, &conn_info.DeviceDescriptor, LIBUSB_DT_DEVICE_SIZE); + usbi_localize_device_descriptor(&dev->device_descriptor); + + if (conn_info.CurrentConfigurationValue == 0) { + usbi_dbg(ctx, "found %u configurations for device '%s' but device is not configured (i.e. current config: 0), ignoring it", + dev->device_descriptor.bNumConfigurations, + priv->dev_id); + CloseHandle(hub_handle); + return LIBUSB_ERROR_OTHER; } + priv->active_config = conn_info.CurrentConfigurationValue; + usbi_dbg(ctx, "found %u configurations (current config: %u) for device '%s'", + dev->device_descriptor.bNumConfigurations, priv->active_config, priv->dev_id); + // Cache as many config descriptors as we can cache_config_descriptors(dev, hub_handle); @@ -1286,37 +1267,24 @@ static bool get_dev_port_number(HDEVINFO dev_info, SP_DEVINFO_DATA *dev_info_dat } static int enumerate_hcd_root_hub(struct libusb_context *ctx, const char *dev_id, - uint8_t bus_number, DEVINST devinst) + DEVINST devinst) { - struct libusb_device *dev; - struct winusb_device_priv *priv; - unsigned long session_id; DEVINST child_devinst; + struct libusb_device* dev; if (CM_Get_Child(&child_devinst, devinst, 0) != CR_SUCCESS) { usbi_warn(ctx, "could not get child devinst for '%s'", dev_id); return LIBUSB_SUCCESS; } - session_id = (unsigned long)child_devinst; - dev = usbi_get_device_by_session_id(ctx, session_id); + dev = usbi_get_device_by_session_id(ctx, (unsigned long)child_devinst); if (dev == NULL) { - usbi_err(ctx, "program assertion failed - HCD '%s' child not found", dev_id); + usbi_warn(ctx, "HCD '%s' child not found", dev_id); return LIBUSB_SUCCESS; } - if (dev->bus_number == 0) { - // Only do this once - usbi_dbg(ctx, "assigning HCD '%s' bus number %u", dev_id, bus_number); - dev->bus_number = bus_number; - - if (sscanf(dev_id, "PCI\\VEN_%04hx&DEV_%04hx%*s", &dev->device_descriptor.idVendor, &dev->device_descriptor.idProduct) != 2) - usbi_warn(ctx, "could not infer VID/PID of HCD root hub from '%s'", dev_id); - - priv = usbi_get_device_priv(dev); - priv->root_hub = true; - } - + if (sscanf(dev_id, "PCI\\VEN_%04hx&DEV_%04hx%*s", &dev->device_descriptor.idVendor, &dev->device_descriptor.idProduct) != 2) + usbi_warn(ctx, "could not infer VID/PID of HCD from '%s'", dev_id); libusb_unref_device(dev); return LIBUSB_SUCCESS; } @@ -1480,6 +1448,140 @@ static int set_hid_interface(struct libusb_context *ctx, struct libusb_device *d return LIBUSB_SUCCESS; } +// get the n-th device interface GUID indexed by guid_number +static int get_guid(struct libusb_context *ctx, char *dev_id, HDEVINFO *dev_info, SP_DEVINFO_DATA *dev_info_data, + int guid_number, GUID **if_guid) +{ + DWORD size, reg_type; + HKEY key; + char *guid_string, *new_guid_string; + char *guid, *guid_term; + LONG s; + int pass, guids_left; + int err = LIBUSB_SUCCESS; +#if !defined(ENABLE_LOGGING) + UNUSED(dev_id); +#endif + + key = pSetupDiOpenDevRegKey(*dev_info, dev_info_data, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ); + if (key == INVALID_HANDLE_VALUE) { + usbi_warn(ctx, "Cannot get the additional GUIDs for '%s'", dev_id); + return LIBUSB_ERROR_ACCESS; + } + // Reserve buffer large enough to hold one GUID with two terminating characters + size = MAX_GUID_STRING_LENGTH + 1; + // Allocate memory for storing the guid_string with two extra terminating characters + // This is necessary for parsing the REG_MULTI_SZ type below + guid_string = malloc(size + 2); + if (guid_string == NULL) { + usbi_err(ctx, "failed to alloc guid_string"); + return LIBUSB_ERROR_NO_MEM; + } + + // The 1st pass tries to get the guid. If it fails due to ERROR_MORE_DATA + // then reallocate enough memory for the 2nd pass + for (pass = 0; pass < 2; pass++) { + // Look for both DeviceInterfaceGUIDs *and* DeviceInterfaceGUID, in that order + // If multiple GUIDs, find the n-th that is indexed by guid_number + s = pRegQueryValueExA(key, "DeviceInterfaceGUIDs", NULL, ®_type, + (LPBYTE)guid_string, &size); + if (s == ERROR_FILE_NOT_FOUND) + s = pRegQueryValueExA(key, "DeviceInterfaceGUID", NULL, ®_type, + (LPBYTE)guid_string, &size); + if (s == ERROR_SUCCESS) { + // The GUID was read successfully + break; + } else if (s == ERROR_FILE_NOT_FOUND) { + usbi_info(ctx, "no DeviceInterfaceGUID registered for '%s'", dev_id); + err = LIBUSB_ERROR_ACCESS; + goto exit; + } else if (s == ERROR_MORE_DATA) { + if (pass == 1) { + // Previous pass should have allocated enough memory, but reading failed + usbi_warn(ctx, "unexpected error from pRegQueryValueExA for '%s'", dev_id); + err = LIBUSB_ERROR_OTHER; + goto exit; + } + new_guid_string = realloc((void *)guid_string, size + 2); + if (new_guid_string == NULL) { + usbi_err(ctx, "failed to realloc guid string"); + err = LIBUSB_ERROR_NO_MEM; + goto exit; + } + guid_string = new_guid_string; + } else { + usbi_warn(ctx, "unexpected error from pRegQueryValueExA for '%s'", dev_id); + err = LIBUSB_ERROR_ACCESS; + goto exit; + } + } + + // https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regqueryvalueexa#remarks + // - "string may not have been stored with the proper terminating null characters" + // - The following GUIDs should be consider as valid: + // "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}\0", "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}", + // "{xxx.....xx}\0\0\0", "{xxx.....xx}\0{xxx.....xx}\0{xxx.....xx}\0", + // "{xxx.....xx}\0{xxx.....xx}\0{xxx.....xx}", "{xxx.....xx}{xxx.....xx}{xxx.....xx}", + // "{xxx.....xx}\0{xxx.....xx}\0{xxx.....xx}\0\0\0\0" + if ((reg_type == REG_SZ ) || (reg_type == REG_MULTI_SZ)) { + /* Get the n-th GUID indexed by guid_number since the DeviceInterfaceGUIDs may + contain more GUIDs */ + guid = guid_string; + // Add two terminating chars for not overrunning the allocated memory while iterating + guid[size] = '\0'; + guid[size + 1] = '\0'; + // Iterate the GUIDs in the guid string + guids_left = guid_number; + while (guids_left) { + guid = strchr(guid, '}'); + if (guid == NULL) { + usbi_warn(ctx, "no GUID with index %d registered for '%s'", guid_number, dev_id); + err = LIBUSB_ERROR_ACCESS; + goto exit; + } + guid++; + // Skip the terminating char if available + if (*guid == '\0') { + guid++; + } + guids_left--; + } + // Add terminating char to the string + guid_term = strchr(guid, '}'); + if (guid_term == NULL) { + usbi_warn(ctx, "no GUID with index %d registered for '%s'", guid_number, dev_id); + err = LIBUSB_ERROR_ACCESS; + goto exit; + } + // Terminate the current guid string to handle the variant without separators + guid_term++; + *guid_term = '\0'; + } else { + usbi_warn(ctx, "unexpected type of DeviceInterfaceGUID for '%s'", dev_id); + err = LIBUSB_ERROR_ACCESS; + goto exit; + } + + *if_guid = malloc(sizeof(GUID)); + if (*if_guid == NULL) { + usbi_err(ctx, "failed to alloc if_guid"); + err = LIBUSB_ERROR_NO_MEM; + goto exit; + } + if (!string_to_guid(guid, *if_guid)) { + usbi_warn(ctx, "device '%s' has malformed DeviceInterfaceGUID string '%s', skipping", dev_id, guid); + free(*if_guid); + *if_guid = NULL; + err = LIBUSB_ERROR_NO_MEM; + goto exit; + } + +exit: + pRegCloseKey(key); + free(guid_string); + return err; +} + /* * get_device_list: libusb backend device enumeration function */ @@ -1492,18 +1594,19 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_ GUID hid_guid; int r = LIBUSB_SUCCESS; int api, sub_api; - unsigned int pass, i, j; + unsigned int pass, pass_type, i, j; char enumerator[16]; char dev_id[MAX_PATH_LENGTH]; struct libusb_device *dev, *parent_dev; struct winusb_device_priv *priv, *parent_priv; char *dev_interface_path = NULL; unsigned long session_id; - DWORD size, port_nr, reg_type, install_state; - HKEY key; + DWORD size, port_nr, install_state; + uint8_t bus_number = 0; +#if defined(ENABLE_LOGGING) char guid_string[MAX_GUID_STRING_LENGTH]; +#endif GUID *if_guid; - LONG s; #define HUB_PASS 0 #define DEV_PASS 1 #define HCD_PASS 2 @@ -1524,14 +1627,16 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_ libusb_device **unref_list, **new_unref_list; unsigned int unref_size = UNREF_SIZE_STEP; unsigned int unref_cur = 0; + DWORD hub_port_nr; - // PASS 1 : (re)enumerate HCDs (allows for HCD hotplug) - // PASS 2 : (re)enumerate HUBS - // PASS 3 : (re)enumerate generic USB devices (including driverless) - // and list additional USB device interface GUIDs to explore - // PASS 4 : (re)enumerate master USB devices that have a device interface - // PASS 5+: (re)enumerate device interfaced GUIDs (including HID) and - // set the device interfaces. + // PASS 0 : enumerate HUBs + // PASS 1 : (re)enumerate master devices that have a DEVice interface + // PASS 2 : (re)enumerate HCDs (allow for HCD hotplug) + // PASS 3 : (re)enumerate GENeric devices (including driverless) + // and list additional device interface GUIDs to explore + // PASS 4 : (re)enumerate device interface GUIDs (including HID) + // and set the device interfaces + // PASS 5+: (re)enumerate additional EXTra GUID devices // Init the GUID table guid_list = malloc(guid_size * sizeof(void *)); @@ -1568,10 +1673,10 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_ } for (pass = 0; ((pass < nb_guids) && (r == LIBUSB_SUCCESS)); pass++) { -//#define ENUM_DEBUG -#if defined(ENABLE_LOGGING) && defined(ENUM_DEBUG) + pass_type = MIN(pass, EXT_PASS); +#if defined(ENABLE_LOGGING) const char * const passname[] = {"HUB", "DEV", "HCD", "GEN", "HID", "EXT"}; - usbi_dbg(ctx, "#### PROCESSING %ss %s", passname[MIN(pass, EXT_PASS)], guid_to_string(guid_list[pass], guid_string)); + usbi_dbg(ctx, "ENUM pass %s %s", passname[pass_type], guid_to_string(guid_list[pass], guid_string)); #endif if ((pass == HID_PASS) && (guid_list[HID_PASS] == NULL)) continue; @@ -1589,11 +1694,6 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_ if (r != LIBUSB_SUCCESS) break; - if ((pass == HCD_PASS) && (i == UINT8_MAX)) { - usbi_warn(ctx, "program assertion failed - found more than %u buses, skipping the rest", UINT8_MAX); - break; - } - if (pass != GEN_PASS) { // Except for GEN, all passes deal with device interfaces r = get_interface_details(ctx, *dev_info, &dev_info_data, guid_list[pass], &_index, &dev_interface_path); @@ -1622,14 +1722,12 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_ continue; } -#ifdef ENUM_DEBUG - usbi_dbg(ctx, "PRO: %s", dev_id); -#endif + usbi_dbg(ctx, "ENUM processing %s", dev_id); // Set API to use or get additional data from generic pass api = USB_API_UNSUPPORTED; sub_api = SUB_API_NOTSET; - switch (pass) { + switch (pass_type) { case HCD_PASS: break; case HUB_PASS: @@ -1668,68 +1766,44 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_ usbi_info(ctx, "libusb will not be able to access it"); } // ...and to add the additional device interface GUIDs - key = pSetupDiOpenDevRegKey(*dev_info, &dev_info_data, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ); - if (key == INVALID_HANDLE_VALUE) - break; - // Look for both DeviceInterfaceGUIDs *and* DeviceInterfaceGUID, in that order - // If multiple GUIDs just process the first and ignore the others - size = sizeof(guid_string); - s = pRegQueryValueExA(key, "DeviceInterfaceGUIDs", NULL, ®_type, - (LPBYTE)guid_string, &size); - if (s == ERROR_FILE_NOT_FOUND) - s = pRegQueryValueExA(key, "DeviceInterfaceGUID", NULL, ®_type, - (LPBYTE)guid_string, &size); - pRegCloseKey(key); - if (s == ERROR_FILE_NOT_FOUND) { - break; /* no DeviceInterfaceGUID registered */ - } else if (s != ERROR_SUCCESS && s != ERROR_MORE_DATA) { - usbi_warn(ctx, "unexpected error from pRegQueryValueExA for '%s'", dev_id); - break; - } - // https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regqueryvalueexa#remarks - // - "string may not have been stored with the proper terminating null characters" - // - "Note that REG_MULTI_SZ strings could have two terminating null characters" - if ((reg_type == REG_SZ && size >= sizeof(guid_string) - sizeof(char)) - || (reg_type == REG_MULTI_SZ && size >= sizeof(guid_string) - 2 * sizeof(char))) { - if (nb_guids == guid_size) { - new_guid_list = realloc((void *)guid_list, (guid_size + GUID_SIZE_STEP) * sizeof(void *)); - if (new_guid_list == NULL) { - usbi_err(ctx, "failed to realloc guid list"); - LOOP_BREAK(LIBUSB_ERROR_NO_MEM); - } - guid_list = new_guid_list; - guid_size += GUID_SIZE_STEP; - } - if_guid = malloc(sizeof(*if_guid)); - if (if_guid == NULL) { - usbi_err(ctx, "failed to alloc if_guid"); - LOOP_BREAK(LIBUSB_ERROR_NO_MEM); + r = get_guid(ctx, dev_id, dev_info, &dev_info_data, 0, &if_guid); + if (r == LIBUSB_SUCCESS) { + // Check if we've already seen this GUID + for (j = EXT_PASS; j < nb_guids; j++) { + if (memcmp(guid_list[j], if_guid, sizeof(*if_guid)) == 0) + break; } - if (!string_to_guid(guid_string, if_guid)) { - usbi_warn(ctx, "device '%s' has malformed DeviceInterfaceGUID string '%s', skipping", dev_id, guid_string); - free(if_guid); - } else { - // Check if we've already seen this GUID - for (j = EXT_PASS; j < nb_guids; j++) { - if (memcmp(guid_list[j], if_guid, sizeof(*if_guid)) == 0) - break; - } - if (j == nb_guids) { - usbi_dbg(ctx, "extra GUID: %s", guid_string); - guid_list[nb_guids++] = if_guid; - } else { - // Duplicate, ignore - free(if_guid); + if (j == nb_guids) { + usbi_dbg(ctx, "extra GUID: %s", guid_to_string(if_guid, guid_string)); + // Extend the guid_list capacity if needed + if (nb_guids == guid_size) { + new_guid_list = realloc((void *)guid_list, (guid_size + GUID_SIZE_STEP) * sizeof(void *)); + if (new_guid_list == NULL) { + usbi_err(ctx, "failed to realloc guid list"); + free(if_guid); + LOOP_BREAK(LIBUSB_ERROR_NO_MEM); + } + guid_list = new_guid_list; + guid_size += GUID_SIZE_STEP; } + guid_list[nb_guids++] = if_guid; + } else { + // Duplicate, ignore + free(if_guid); } + } else if (r == LIBUSB_ERROR_ACCESS) { + r = LIBUSB_SUCCESS; + } else if (r == LIBUSB_ERROR_NO_MEM) { + LOOP_BREAK(LIBUSB_ERROR_NO_MEM); } else { - usbi_warn(ctx, "unexpected type/size of DeviceInterfaceGUID for '%s'", dev_id); + usbi_warn(ctx, "unexpected error during getting DeviceInterfaceGUID for '%s'", dev_id); } break; case HID_PASS: api = USB_API_HID; break; - default: + case DEV_PASS: + case EXT_PASS: // Get the API type (after checking that the driver installation is OK) if ((!pSetupDiGetDeviceRegistryPropertyA(*dev_info, &dev_info_data, SPDRP_INSTALL_STATE, NULL, (PBYTE)&install_state, sizeof(install_state), &size)) || (size != sizeof(install_state))) { @@ -1742,6 +1816,8 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_ } get_api_type(dev_info, &dev_info_data, &api, &sub_api); break; + default: + assert(false); // unreachable since all pass types covered explicitly } // Find parent device (for the passes that need it) @@ -1821,7 +1897,7 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_ } // Setup device - switch (pass) { + switch (pass_type) { case HUB_PASS: case DEV_PASS: // If the device has already been setup, don't do it again @@ -1834,7 +1910,22 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_ priv->sub_api = sub_api; switch (api) { case USB_API_COMPOSITE: + break; case USB_API_HUB: + parent_dev = get_ancestor(ctx, dev_info_data.DevInst, NULL); + if (parent_dev == NULL) { + if (!get_dev_port_number(*dev_info, &dev_info_data, &hub_port_nr) || hub_port_nr == 0) { + if (bus_number == UINT8_MAX) { + usbi_warn(ctx, "program assertion failed - found more than %u buses, skipping the rest", UINT8_MAX); + break; + } + priv->root_hub = true; + dev->bus_number = ++bus_number; + usbi_dbg(ctx, "assigning Root Hub '%s' bus number %u", dev_id, bus_number); + } + } else { + libusb_unref_device(parent_dev); + } break; case USB_API_HID: priv->hid = calloc(1, sizeof(struct hid_device_priv)); @@ -1854,7 +1945,7 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_ } break; case HCD_PASS: - r = enumerate_hcd_root_hub(ctx, dev_id, (uint8_t)(i + 1), dev_info_data.DevInst); + r = enumerate_hcd_root_hub(ctx, dev_id, dev_info_data.DevInst); break; case GEN_PASS: port_nr = 0; @@ -1875,7 +1966,8 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_ r = LIBUSB_SUCCESS; } break; - default: // HID_PASS and later + case HID_PASS: + case EXT_PASS: if (parent_priv->apib->id == USB_API_HID || parent_priv->apib->id == USB_API_COMPOSITE) { if (parent_priv->apib->id == USB_API_HID) { usbi_dbg(ctx, "setting HID interface for [%lX]:", parent_dev->session_data); @@ -1899,6 +1991,8 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_ } libusb_unref_device(parent_dev); break; + default: + assert(false); // unreachable since all pass types covered explicitly } } } @@ -2177,111 +2271,6 @@ static enum libusb_transfer_status winusb_copy_transfer_data(struct usbi_transfe return priv->apib->copy_transfer_data(SUB_API_NOTSET, itransfer, length); } -static int winusb_set_option(struct libusb_context *ctx, enum libusb_option option, va_list ap) -{ - struct libusb_device_handle *dev_handle; - unsigned int endpoint; - unsigned int enable; - unsigned int *max_transfer_size_ptr; - struct winusb_device_handle_priv *handle_priv; - struct winusb_device_priv *priv; - UCHAR policy; - int current_interface; - HANDLE winusb_handle; - int sub_api = SUB_API_NOTSET; - ULONG max_transfer_size = 0; - - switch (option) { - case LIBUSB_OPTION_WINUSB_RAW_IO: - dev_handle = va_arg(ap, struct libusb_device_handle *); - endpoint = va_arg(ap, unsigned int); - enable = va_arg(ap, unsigned int); - max_transfer_size_ptr = va_arg(ap, unsigned int *); - - policy = enable != 0; - - if (dev_handle == NULL) { - usbi_err(ctx, "device handle passed for RAW_IO was NULL"); - return LIBUSB_ERROR_INVALID_PARAM; - } - - if (HANDLE_CTX(dev_handle) != ctx) { - usbi_err(ctx, "device handle passed for RAW_IO has wrong context"); - return LIBUSB_ERROR_INVALID_PARAM; - } - - if (endpoint & ~(LIBUSB_ENDPOINT_DIR_MASK | LIBUSB_ENDPOINT_ADDRESS_MASK)) { - usbi_err(ctx, "invalid endpoint %X passed for RAW_IO", endpoint); - return LIBUSB_ERROR_INVALID_PARAM; - } - - if (!(endpoint & LIBUSB_ENDPOINT_DIR_MASK)) { - usbi_err(ctx, "endpoint %02X passed for RAW_IO is OUT, not IN", endpoint); - return LIBUSB_ERROR_INVALID_PARAM; - } - - handle_priv = get_winusb_device_handle_priv(dev_handle); - priv = usbi_get_device_priv(dev_handle->dev); - current_interface = interface_by_endpoint(priv, handle_priv, (uint8_t) endpoint); - - if (current_interface < 0) { - usbi_err(ctx, "unable to match endpoint to an open interface for RAW_IO"); - return LIBUSB_ERROR_NOT_FOUND; - } - - if (priv->usb_interface[current_interface].apib->id != USB_API_WINUSBX) { - usbi_err(ctx, "interface is not winusb when setting RAW_IO"); - return LIBUSB_ERROR_NOT_SUPPORTED; - } - - winusb_handle = handle_priv->interface_handle[current_interface].api_handle; - - if (!HANDLE_VALID(winusb_handle)) { - usbi_err(HANDLE_CTX(dev_handle), "WinUSB handle not valid when setting RAW_IO"); - return LIBUSB_ERROR_NOT_FOUND; - } - - CHECK_WINUSBX_AVAILABLE(sub_api); - - if (enable && max_transfer_size_ptr != NULL) { - ULONG size = sizeof(ULONG); - if (!WinUSBX[sub_api].GetPipePolicy(winusb_handle, (UCHAR) endpoint, - MAXIMUM_TRANSFER_SIZE, &size, &max_transfer_size)) { - usbi_err(ctx, "failed to get MAXIMUM_TRANSFER_SIZE for endpoint %02X", endpoint); - switch (GetLastError()) { - case ERROR_INVALID_HANDLE: - return LIBUSB_ERROR_INVALID_PARAM; - default: - return LIBUSB_ERROR_OTHER; - } - } - } - - if (!WinUSBX[sub_api].SetPipePolicy(winusb_handle, (UCHAR) endpoint, - RAW_IO, sizeof(UCHAR), &policy)) { - usbi_err(ctx, "failed to change RAW_IO for endpoint %02X", endpoint); - switch (GetLastError()) { - case ERROR_INVALID_HANDLE: - case ERROR_INVALID_PARAMETER: - return LIBUSB_ERROR_INVALID_PARAM; - case ERROR_NOT_ENOUGH_MEMORY: - return LIBUSB_ERROR_NO_MEM; - default: - return LIBUSB_ERROR_OTHER; - } - } - - usbi_dbg(ctx, "%s RAW_IO for endpoint %02X", enable ? "enabled" : "disabled", endpoint); - - if (enable && max_transfer_size_ptr != NULL) - *max_transfer_size_ptr = max_transfer_size; - - return LIBUSB_SUCCESS; - default: - return LIBUSB_ERROR_NOT_SUPPORTED; - } -} - // NB: MSVC6 does not support named initializers. const struct windows_backend winusb_backend = { winusb_init, @@ -2304,14 +2293,16 @@ const struct windows_backend winusb_backend = { winusb_cancel_transfer, winusb_clear_transfer_priv, winusb_copy_transfer_data, - winusb_set_option, }; /* * USB API backends */ -static const char * const composite_driver_names[] = {"USBCCGP"}; +static const char * const composite_driver_names[] = { + "USBCCGP", // (Windows built-in) USB Composite Device + "dg_ssudbus" // SAMSUNG Mobile USB Composite Device +}; static const char * const winusbx_driver_names[] = {"libusbK", "libusb0", "WinUSB"}; static const char * const hid_driver_names[] = {"HIDUSB", "MOUHID", "KBDHID"}; const struct windows_usb_api_backend usb_api_backend[USB_API_MAX] = { diff --git a/libusb/libusb/os/windows_winusb.h b/libusb/libusb/os/windows_winusb.h index 437a33f..992f58b 100644 --- a/libusb/libusb/os/windows_winusb.h +++ b/libusb/libusb/os/windows_winusb.h @@ -33,7 +33,7 @@ #define MAX_USB_STRING_LENGTH 128 #define MAX_HID_REPORT_SIZE 1024 #define MAX_HID_DESCRIPTOR_SIZE 256 -#define MAX_GUID_STRING_LENGTH 40 +#define MAX_GUID_STRING_LENGTH 39 #define MAX_PATH_LENGTH 256 #define MAX_KEY_LENGTH 256 #define LIST_SEPARATOR ';' diff --git a/libusb/libusb/strerror.c b/libusb/libusb/strerror.c index e88a763..eb35503 100644 --- a/libusb/libusb/strerror.c +++ b/libusb/libusb/strerror.c @@ -28,7 +28,7 @@ *
  • Download the latest \c strerror.c from:
    * https://raw.github.com/libusb/libusb/master/libusb/strerror.c
  • *
  • Open the file in an UTF-8 capable editor
  • - *
  • Add the 2 letter ISO 639-1 + *
  • Add the 2 letter ISO 639-1 * code for your locale at the end of \c usbi_locale_supported[]
    * Eg. for Chinese, you would add "zh" so that: * \code... usbi_locale_supported[] = { "en", "nl", "fr" };\endcode @@ -160,7 +160,7 @@ static const char * const (*usbi_error_strings)[LIBUSB_ERROR_COUNT] = &usbi_loca * If libusb_setlocale() is not called, all messages will be in English. * * The following functions return translatable strings: libusb_strerror(). - * Note that the libusb log messages controlled through libusb_set_debug() + * Note that the libusb log messages controlled through LIBUSB_OPTION_LOG_LEVEL * are not translated, they are always in English. * * For POSIX UTF-8 environments if you want libusb to follow the standard diff --git a/libusb/libusb/version.h b/libusb/libusb/version.h index fe95d84..ca9df20 100644 --- a/libusb/libusb/version.h +++ b/libusb/libusb/version.h @@ -7,7 +7,7 @@ #define LIBUSB_MINOR 0 #endif #ifndef LIBUSB_MICRO -#define LIBUSB_MICRO 26 +#define LIBUSB_MICRO 27 #endif #ifndef LIBUSB_NANO #define LIBUSB_NANO 0 diff --git a/libusb/libusb/version_nano.h b/libusb/libusb/version_nano.h index f4f74b4..a6165f3 100644 --- a/libusb/libusb/version_nano.h +++ b/libusb/libusb/version_nano.h @@ -1 +1 @@ -#define LIBUSB_NANO 11807 +#define LIBUSB_NANO 11882 diff --git a/libusb/msvc/init_context.vcxproj b/libusb/msvc/init_context.vcxproj new file mode 100644 index 0000000..2dc3ba2 --- /dev/null +++ b/libusb/msvc/init_context.vcxproj @@ -0,0 +1,35 @@ + + + + + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7} + + + + + + + + + + + + + + + + + + + + + + + {349ee8f9-7d25-4909-aaf5-ff3fade72187} + false + + + + + + diff --git a/libusb/msvc/libusb.sln b/libusb/msvc/libusb.sln index c551fc8..66bf417 100644 --- a/libusb/msvc/libusb.sln +++ b/libusb/msvc/libusb.sln @@ -16,6 +16,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "getopt", "getopt.vcxproj", EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hotplugtest", "hotplugtest.vcxproj", "{99D2AC64-DC66-4422-91CE-6715C403C9E5}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "init_context", "init_context.vcxproj", "{FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}" +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "listdevs", "listdevs.vcxproj", "{F4938DB0-3DE7-4737-9C5A-EAD1BE819F87}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sam3u_benchmark", "sam3u_benchmark.vcxproj", "{861CDD5F-59A2-4F34-957E-5C1AF98BE0A4}" @@ -274,6 +276,38 @@ Global {99D2AC64-DC66-4422-91CE-6715C403C9E5}.Release-MT|Win32.Build.0 = Release|Win32 {99D2AC64-DC66-4422-91CE-6715C403C9E5}.Release-MT|x64.ActiveCfg = Release|x64 {99D2AC64-DC66-4422-91CE-6715C403C9E5}.Release-MT|x64.Build.0 = Release|x64 + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Debug|ARM.ActiveCfg = Debug|ARM + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Debug|ARM.Build.0 = Debug|ARM + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Debug|ARM64.Build.0 = Debug|ARM64 + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Debug|Win32.ActiveCfg = Debug|Win32 + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Debug|Win32.Build.0 = Debug|Win32 + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Debug|x64.ActiveCfg = Debug|x64 + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Debug|x64.Build.0 = Debug|x64 + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Debug-MT|ARM.ActiveCfg = Debug-MT|ARM + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Debug-MT|ARM.Build.0 = Debug-MT|ARM + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Debug-MT|ARM64.ActiveCfg = Debug-MT|ARM64 + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Debug-MT|ARM64.Build.0 = Debug-MT|ARM64 + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Debug-MT|Win32.ActiveCfg = Debug-MT|Win32 + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Debug-MT|Win32.Build.0 = Debug-MT|Win32 + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Debug-MT|x64.ActiveCfg = Debug-MT|x64 + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Debug-MT|x64.Build.0 = Debug-MT|x64 + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Release|ARM.ActiveCfg = Release|ARM + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Release|ARM.Build.0 = Release|ARM + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Release|ARM64.ActiveCfg = Release|ARM64 + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Release|ARM64.Build.0 = Release|ARM64 + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Release|Win32.ActiveCfg = Release|Win32 + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Release|Win32.Build.0 = Release|Win32 + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Release|x64.ActiveCfg = Release|x64 + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Release|x64.Build.0 = Release|x64 + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Release-MT|ARM.ActiveCfg = Release-MT|ARM + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Release-MT|ARM.Build.0 = Release-MT|ARM + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Release-MT|ARM64.ActiveCfg = Release-MT|ARM64 + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Release-MT|ARM64.Build.0 = Release-MT|ARM64 + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Release-MT|Win32.ActiveCfg = Release-MT|Win32 + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Release-MT|Win32.Build.0 = Release-MT|Win32 + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Release-MT|x64.ActiveCfg = Release-MT|x64 + {FB388436-7D25-4A7E-BD3C-DF4EE9FCC8F7}.Release-MT|x64.Build.0 = Release-MT|x64 {F4938DB0-3DE7-4737-9C5A-EAD1BE819F87}.Debug|ARM.ActiveCfg = Debug|ARM {F4938DB0-3DE7-4737-9C5A-EAD1BE819F87}.Debug|ARM.Build.0 = Debug|ARM {F4938DB0-3DE7-4737-9C5A-EAD1BE819F87}.Debug|ARM64.ActiveCfg = Debug|ARM64 diff --git a/libusb/tests/Makefile.am b/libusb/tests/Makefile.am index 94ed32c..c39b2bb 100644 --- a/libusb/tests/Makefile.am +++ b/libusb/tests/Makefile.am @@ -1,14 +1,30 @@ AM_CPPFLAGS = -I$(top_srcdir)/libusb LDADD = ../libusb/libusb-1.0.la -LIBS = +AM_LDFLAGS = -static stress_SOURCES = stress.c testlib.c stress_mt_SOURCES = stress_mt.c set_option_SOURCES = set_option.c testlib.c init_context_SOURCES = init_context.c testlib.c +macos_SOURCES = macos.c testlib.c + +stress_mt_CFLAGS = $(AM_CFLAGS) $(THREAD_CFLAGS) +stress_mt_LDADD = $(LDADD) $(THREAD_LIBS) +stress_mt_LDFLAGS = $(AM_LDFLAGS) + +if OS_EMSCRIPTEN +# On the Web you can't block the main thread as this blocks the event loop itself, +# causing deadlocks when trying to use async APIs like WebUSB. +# We use the PROXY_TO_PTHREAD Emscripten's feature to move the main app to a separate thread +# where it can block safely. +stress_mt_LDFLAGS += ${AM_LDFLAGS} -s PROXY_TO_PTHREAD -s EXIT_RUNTIME +endif noinst_HEADERS = libusb_testlib.h noinst_PROGRAMS = stress stress_mt set_option init_context +if OS_DARWIN +noinst_PROGRAMS += macos +endif if BUILD_UMOCKDEV_TEST # NOTE: We add libumockdev-preload.so so that we can run tests in-process @@ -20,3 +36,5 @@ umockdev_SOURCES = umockdev.c noinst_PROGRAMS += umockdev endif + +TESTS=$(noinst_PROGRAMS) diff --git a/libusb/tests/init_context.c b/libusb/tests/init_context.c index 757afe9..004848c 100644 --- a/libusb/tests/init_context.c +++ b/libusb/tests/init_context.c @@ -33,12 +33,6 @@ static int unsetenv(const char *env) { return _putenv_s(env, ""); } - -static int setenv(const char *env, const char *value, int overwrite) { - if (getenv(env) && !overwrite) - return 0; - return _putenv_s(env, value); -} #endif #define LIBUSB_TEST_CLEAN_EXIT(code) \ @@ -64,7 +58,7 @@ static int setenv(const char *env, const char *value, int overwrite) { } while (0) /** - * Use relational operatator to compare two values and fail the test if the + * Use relational operator to compare two values and fail the test if the * comparison is false. Intended to compare integer or pointer types. * * Example: LIBUSB_EXPECT(==, 0, 1) -> fail, LIBUSB_EXPECT(==, 0, 0) -> ok. @@ -109,15 +103,15 @@ static libusb_testlib_result test_init_context_log_level(void) { LIBUSB_TEST_RETURN_ON_ERROR(libusb_init_context(&test_ctx, options, /*num_options=*/1)); -#ifndef ENABLE_DEBUG_LOGGING +#if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING) LIBUSB_EXPECT(==, test_ctx->debug, LIBUSB_LOG_LEVEL_ERROR); #endif LIBUSB_TEST_CLEAN_EXIT(TEST_STATUS_SUCCESS); } -static void test_log_cb(libusb_context *ctx, enum libusb_log_level level, - const char *str) { +static void LIBUSB_CALL test_log_cb(libusb_context *ctx, enum libusb_log_level level, + const char *str) { UNUSED(ctx); UNUSED(level); UNUSED(str); @@ -139,7 +133,7 @@ static libusb_testlib_result test_init_context_log_cb(void) { LIBUSB_TEST_RETURN_ON_ERROR(libusb_init_context(&test_ctx, options, /*num_options=*/1)); -#ifndef ENABLE_DEBUG_LOGGING +#if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING) LIBUSB_EXPECT(==, test_ctx->log_handler, test_log_cb); #endif diff --git a/libusb/tests/macos.c b/libusb/tests/macos.c new file mode 100644 index 0000000..a7b1231 --- /dev/null +++ b/libusb/tests/macos.c @@ -0,0 +1,130 @@ +/* -*- Mode: C; indent-tabs-mode:nil -*- */ +/* + * Unit tests for libusb_set_option + * Copyright © 2023 Nathan Hjelm + * Copyright © 2023 Google, LLC. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include +#include +#include +#include +#include "libusbi.h" +#include "libusb_testlib.h" + +#define LIBUSB_TEST_CLEAN_EXIT(code) \ + do { \ + if (test_ctx != NULL) { \ + libusb_exit(test_ctx); \ + } \ + unsetenv("LIBUSB_DEBUG"); \ + return (code); \ + } while (0) + +/** + * Fail the test if the expression does not evaluate to LIBUSB_SUCCESS. + */ +#define LIBUSB_TEST_RETURN_ON_ERROR(expr) \ + do { \ + int _result = (expr); \ + if (LIBUSB_SUCCESS != _result) { \ + libusb_testlib_logf("Not success (%s) at %s:%d", #expr, \ + __FILE__, __LINE__); \ + LIBUSB_TEST_CLEAN_EXIT(TEST_STATUS_FAILURE); \ + } \ + } while (0) + +/** + * Use relational operator to compare two values and fail the test if the + * comparison is false. Intended to compare integer or pointer types. + * + * Example: LIBUSB_EXPECT(==, 0, 1) -> fail, LIBUSB_EXPECT(==, 0, 0) -> ok. + */ +#define LIBUSB_EXPECT(operator, lhs, rhs) \ + do { \ + int64_t _lhs = (lhs), _rhs = (rhs); \ + if (!(_lhs operator _rhs)) { \ + libusb_testlib_logf("Expected %s (%" PRId64 ") " #operator \ + " %s (%" PRId64 ") at %s:%d", #lhs, \ + (int64_t)(intptr_t)_lhs, #rhs, \ + (int64_t)(intptr_t)_rhs, __FILE__, \ + __LINE__); \ + LIBUSB_TEST_CLEAN_EXIT(TEST_STATUS_FAILURE); \ + } \ + } while (0) + + +extern uint32_t libusb_testonly_fake_running_version; +extern int libusb_testonly_using_running_interface_version; +extern int libusb_testonly_using_running_device_version; +extern bool libusb_testonly_clear_running_version_cache; + +static libusb_testlib_result test_macos_version_fallback(void) { + libusb_context *test_ctx = NULL; + libusb_testonly_fake_running_version = 100001; + libusb_testonly_clear_running_version_cache = true; + + LIBUSB_TEST_RETURN_ON_ERROR(libusb_init_context(&test_ctx, /*options=*/NULL, + /*num_options=*/0)); + LIBUSB_EXPECT(==, libusb_testonly_using_running_interface_version, 220); + LIBUSB_EXPECT(==, libusb_testonly_using_running_device_version, 197); + + libusb_exit(test_ctx); + test_ctx = NULL; + + libusb_testonly_fake_running_version = 100900; + + LIBUSB_TEST_RETURN_ON_ERROR(libusb_init_context(&test_ctx, /*options=*/NULL, + /*num_options=*/0)); + LIBUSB_EXPECT(==, libusb_testonly_using_running_interface_version, 650); + LIBUSB_EXPECT(==, libusb_testonly_using_running_device_version, 650); + + libusb_exit(test_ctx); + test_ctx = NULL; + + libusb_testonly_fake_running_version = 101200; + + LIBUSB_TEST_RETURN_ON_ERROR(libusb_init_context(&test_ctx, /*options=*/NULL, + /*num_options=*/0)); + LIBUSB_EXPECT(==, libusb_testonly_using_running_interface_version, 800); + LIBUSB_EXPECT(==, libusb_testonly_using_running_device_version, 650); + + libusb_exit(test_ctx); + test_ctx = NULL; + + // Test a version smaller than 10.0. Initialization should fail. + libusb_testonly_fake_running_version = 99999; + + int error = libusb_init_context(&test_ctx, /*options=*/NULL, + /*num_options=*/0); + LIBUSB_EXPECT(!=, error, LIBUSB_SUCCESS); + + + LIBUSB_TEST_CLEAN_EXIT(TEST_STATUS_SUCCESS); +} + +static const libusb_testlib_test tests[] = { + { "test_macos_version_fallback", &test_macos_version_fallback }, + LIBUSB_NULL_TEST +}; + +int main(int argc, char *argv[]) +{ + return libusb_testlib_run_tests(argc, argv, tests); +} diff --git a/libusb/tests/set_option.c b/libusb/tests/set_option.c index df2ae1d..d7786a1 100644 --- a/libusb/tests/set_option.c +++ b/libusb/tests/set_option.c @@ -30,6 +30,7 @@ #if defined(_WIN32) && !defined(__CYGWIN__) #include +#if defined(ENABLE_LOGGING) static int unsetenv(const char *env) { return _putenv_s(env, ""); } @@ -40,6 +41,7 @@ static int setenv(const char *env, const char *value, int overwrite) { return _putenv_s(env, value); } #endif +#endif #define LIBUSB_TEST_CLEAN_EXIT(code) \ do { \ @@ -64,7 +66,7 @@ static int setenv(const char *env, const char *value, int overwrite) { } while (0) /** - * Use relational operatator to compare two values and fail the test if the + * Use relational operator to compare two values and fail the test if the * comparison is false. Intended to compare integer or pointer types. * * Example: LIBUSB_EXPECT(==, 0, 1) -> fail, LIBUSB_EXPECT(==, 0, 0) -> ok. @@ -172,8 +174,13 @@ static libusb_testlib_result test_no_discovery(void) ssize_t num_devices = libusb_get_device_list(test_ctx, &device_list); libusb_free_device_list(device_list, /*unref_devices=*/1); libusb_exit(test_ctx); + test_ctx = NULL; - LIBUSB_EXPECT(>, num_devices, 0); + if (num_devices == 0) { + libusb_testlib_logf("Warning: no devices found, the test will only verify that setting LIBUSB_OPTION_NO_DEVICE_DISCOVERY succeeds."); + } + + LIBUSB_EXPECT(>=, num_devices, 0); LIBUSB_TEST_RETURN_ON_ERROR(libusb_set_option(NULL, LIBUSB_OPTION_NO_DEVICE_DISCOVERY)); LIBUSB_TEST_RETURN_ON_ERROR(libusb_init_context(&test_ctx, /*options=*/NULL, @@ -189,12 +196,14 @@ static libusb_testlib_result test_no_discovery(void) #endif } -static void test_log_cb(libusb_context *ctx, enum libusb_log_level level, +#if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING) +static void LIBUSB_CALL test_log_cb(libusb_context *ctx, enum libusb_log_level level, const char *str) { UNUSED(ctx); UNUSED(level); UNUSED(str); } +#endif static libusb_testlib_result test_set_log_cb(void) diff --git a/libusb/tests/stress_mt.c b/libusb/tests/stress_mt.c index 430282e..3a8f321 100644 --- a/libusb/tests/stress_mt.c +++ b/libusb/tests/stress_mt.c @@ -21,6 +21,7 @@ #include #include +#include #if defined(PLATFORM_POSIX) @@ -41,6 +42,8 @@ static inline void thread_join(thread_t thread) (void)pthread_join(thread, NULL); } +#include + #elif defined(PLATFORM_WINDOWS) typedef HANDLE thread_t; @@ -70,12 +73,17 @@ static inline void thread_join(thread_t thread) (void)WaitForSingleObject(thread, INFINITE); (void)CloseHandle(thread); } + +typedef volatile LONG atomic_bool; + +#define atomic_exchange InterlockedExchange #endif /* PLATFORM_WINDOWS */ /* Test that creates and destroys contexts repeatedly */ #define NTHREADS 8 #define ITERS 64 +#define MAX_DEVCOUNT 128 struct thread_info { int number; @@ -85,28 +93,107 @@ struct thread_info { int iteration; } tinfo[NTHREADS]; +atomic_bool no_access[MAX_DEVCOUNT]; + +/* Function called by backend during device initialization to convert + * multi-byte fields in the device descriptor to host-endian format. + * Copied from libusbi.h as we want test to be realistic and not depend on internals. + */ +static inline void usbi_localize_device_descriptor(struct libusb_device_descriptor *desc) +{ + desc->bcdUSB = libusb_le16_to_cpu(desc->bcdUSB); + desc->idVendor = libusb_le16_to_cpu(desc->idVendor); + desc->idProduct = libusb_le16_to_cpu(desc->idProduct); + desc->bcdDevice = libusb_le16_to_cpu(desc->bcdDevice); +} + static thread_return_t THREAD_CALL_TYPE init_and_exit(void * arg) { struct thread_info *ti = (struct thread_info *) arg; - for (int i = 0; i < ITERS; ++i) { + for (ti->iteration = 0; ti->iteration < ITERS && !ti->err; ti->iteration++) { libusb_context *ctx = NULL; - int r; - r = libusb_init_context(&ctx, /*options=*/NULL, /*num_options=*/0); - if (r != LIBUSB_SUCCESS) { - ti->err = r; - ti->iteration = i; - return (thread_return_t) THREAD_RETURN_VALUE; + if ((ti->err = libusb_init_context(&ctx, /*options=*/NULL, /*num_options=*/0)) != 0) { + break; } if (ti->enumerate) { libusb_device **devs; ti->devcount = libusb_get_device_list(ctx, &devs); if (ti->devcount < 0) { - libusb_free_device_list(devs, 1); - ti->iteration = i; + ti->err = (int)ti->devcount; break; } + for (int i = 0; i < ti->devcount && ti->err == 0; i++) { + libusb_device *dev = devs[i]; + struct libusb_device_descriptor desc; + if ((ti->err = libusb_get_device_descriptor(dev, &desc)) != 0) { + break; + } + if (no_access[i]) { + continue; + } + libusb_device_handle *dev_handle; + int open_err = libusb_open(dev, &dev_handle); + if (open_err == LIBUSB_ERROR_ACCESS +#if defined(PLATFORM_WINDOWS) + || open_err == LIBUSB_ERROR_NOT_SUPPORTED + || open_err == LIBUSB_ERROR_NOT_FOUND +#endif + ) { + /* Use atomic swap to ensure we print warning only once across all threads. + This is a warning and not a hard error because it should be fine to run tests + even if we don't have access to some devices. */ + if (!atomic_exchange(&no_access[i], true)) { + fprintf(stderr, "No access to device %04x:%04x, skipping transfer tests.\n", desc.idVendor, desc.idProduct); + } + continue; + } + if (open_err != 0) { + ti->err = open_err; + break; + } + /* Request raw descriptor via control transfer. + This tests opening, transferring and closing from multiple threads in parallel. */ + struct libusb_device_descriptor raw_desc; + int raw_desc_len = libusb_get_descriptor(dev_handle, LIBUSB_DT_DEVICE, 0, (unsigned char *)&raw_desc, sizeof(raw_desc)); + if (raw_desc_len < 0) { + ti->err = raw_desc_len; + goto close; + } + if (raw_desc_len != sizeof(raw_desc)) { + fprintf(stderr, "Thread %d: device %d: unexpected raw descriptor length %d\n", + ti->number, i, raw_desc_len); + ti->err = LIBUSB_ERROR_OTHER; + goto close; + } + usbi_localize_device_descriptor(&raw_desc); +#define ASSERT_EQ(field) if (raw_desc.field != desc.field) { \ + fprintf(stderr, "Thread %d: device %d: mismatch in field " #field ": %d != %d\n", \ + ti->number, i, raw_desc.field, desc.field); \ + ti->err = LIBUSB_ERROR_OTHER; \ + goto close; \ +} + ASSERT_EQ(bLength); + ASSERT_EQ(bDescriptorType); +#if !defined(PLATFORM_WINDOWS) + /* these are hardcoded by the winusbx HID backend */ + ASSERT_EQ(bcdUSB); + ASSERT_EQ(bDeviceClass); + ASSERT_EQ(bDeviceSubClass); + ASSERT_EQ(bDeviceProtocol); + ASSERT_EQ(bMaxPacketSize0); + ASSERT_EQ(bcdDevice); +#endif + ASSERT_EQ(idVendor); + ASSERT_EQ(idProduct); + ASSERT_EQ(iManufacturer); + ASSERT_EQ(iProduct); + ASSERT_EQ(iSerialNumber); + ASSERT_EQ(bNumConfigurations); + close: + libusb_close(dev_handle); + } libusb_free_device_list(devs, 1); } @@ -119,10 +206,14 @@ static int test_multi_init(int enumerate) { thread_t threadId[NTHREADS]; int errs = 0; - int t; + int t, i; + ssize_t last_devcount = 0; + int devcount_mismatch = 0; + int access_failures = 0; printf("Starting %d threads\n", NTHREADS); for (t = 0; t < NTHREADS; t++) { + tinfo[t].err = 0; tinfo[t].number = t; tinfo[t].enumerate = enumerate; thread_create(&threadId[t], &init_and_exit, (void *) &tinfo[t]); @@ -138,21 +229,26 @@ static int test_multi_init(int enumerate) tinfo[t].iteration, libusb_error_name(tinfo[t].err)); } else if (enumerate) { - if (tinfo[t].devcount < 0) { - errs++; - fprintf(stderr, - "Thread %d failed to enumerate devices (iteration %d)\n", - tinfo[t].number, - tinfo[t].iteration); - } else { - printf("Thread %d discovered %ld devices\n", + if (t > 0 && tinfo[t].devcount != last_devcount) { + devcount_mismatch++; + printf("Device count mismatch: Thread %d discovered %ld devices instead of %ld\n", tinfo[t].number, - (long int) tinfo[t].devcount); + (long int) tinfo[t].devcount, + (long int) last_devcount); } + last_devcount = tinfo[t].devcount; } } - return errs; + for (i = 0; i < MAX_DEVCOUNT; i++) + if (no_access[i]) + access_failures++; + + if (enumerate && !devcount_mismatch) + printf("All threads discovered %ld devices (%i not opened)\n", + (long int) last_devcount, access_failures); + + return errs + devcount_mismatch; } int main(void) diff --git a/libusb/tests/testlib.c b/libusb/tests/testlib.c index 3825341..73d9f37 100644 --- a/libusb/tests/testlib.c +++ b/libusb/tests/testlib.c @@ -180,5 +180,5 @@ int libusb_testlib_run_tests(int argc, char *argv[], libusb_testlib_logf("Error in %d tests", error_count); libusb_testlib_logf("Skipped %d tests", skip_count); - return pass_count != run_count; + return fail_count + error_count; } diff --git a/libusb/tests/umockdev.c b/libusb/tests/umockdev.c index 9362b06..a2d457e 100644 --- a/libusb/tests/umockdev.c +++ b/libusb/tests/umockdev.c @@ -33,7 +33,7 @@ #define UNUSED_DATA __attribute__ ((unused)) gconstpointer unused_data /* avoid leak reports inside assertions; leaking stuff on assertion failures does not matter in tests */ -#if !defined(__clang__) +#if !defined(__clang__) && __GNUC__ > 9 #pragma GCC diagnostic ignored "-Wanalyzer-malloc-leak" #pragma GCC diagnostic ignored "-Wanalyzer-file-leak" #endif @@ -89,7 +89,7 @@ typedef struct { GList *flying_urbs; GList *discarded_urbs; - /* GMutex confuses tsan unecessarily */ + /* GMutex confuses TSan unnecessarily */ pthread_mutex_t mutex; } UMockdevTestbedFixture; @@ -245,7 +245,7 @@ handle_ioctl_cb (UMockdevIoctlBase *handler, UMockdevIoctlClient *client, UMockd ioctl_arg = umockdev_ioctl_client_get_arg (client); /* NOTE: We share the address space, dereferencing pointers *will* work. - * However, to make tsan work, we still stick to the API that resolves + * However, to make TSan work, we still stick to the API that resolves * the data into a local copy! */ switch (request) { @@ -432,7 +432,7 @@ test_fixture_setup_libusb(UMockdevTestbedFixture * fixture, int devcount) libusb_init_context(/*ctx=*/&fixture->ctx, /*options=*/NULL, /*num_options=*/0); - /* Supress global log messages completely + /* Suppress global log messages completely * (though, in some tests it might be interesting to check there are no real ones). */ libusb_set_log_cb (NULL, log_handler_null, LIBUSB_LOG_CB_GLOBAL); @@ -876,7 +876,7 @@ transfer_submit_all_retry(TestThreadedSubmit *data) return NULL; } -static void +static void LIBUSB_CALL test_threaded_submit_transfer_cb(struct libusb_transfer *transfer) { TestThreadedSubmit *data = transfer->user_data; @@ -955,7 +955,7 @@ test_threaded_submit(UMockdevTestbedFixture * fixture, UNUSED_DATA) g_free (c); } -static int +static int LIBUSB_CALL hotplug_count_arrival_cb(libusb_context *ctx, libusb_device *device, libusb_hotplug_event event, @@ -972,7 +972,7 @@ hotplug_count_arrival_cb(libusb_context *ctx, } #ifdef UMOCKDEV_HOTPLUG -static int +static int LIBUSB_CALL hotplug_count_removal_cb(libusb_context *ctx, libusb_device *device, libusb_hotplug_event event, diff --git a/libusb/tests/webusb-test-shim/.gitignore b/libusb/tests/webusb-test-shim/.gitignore new file mode 100644 index 0000000..07e6e47 --- /dev/null +++ b/libusb/tests/webusb-test-shim/.gitignore @@ -0,0 +1 @@ +/node_modules diff --git a/libusb/tests/webusb-test-shim/index.js b/libusb/tests/webusb-test-shim/index.js new file mode 100644 index 0000000..703aefd --- /dev/null +++ b/libusb/tests/webusb-test-shim/index.js @@ -0,0 +1,12 @@ +// It's not yet possible to automate actual Chrome's device selection, so +// for now run automated tests via Node.js WebUSB implementation. +// +// It might differ from browser one, but should be enough to catch most obvious issues. + +const { WebUSB } = require('usb'); + +globalThis.navigator = { + usb: new WebUSB({ + allowAllDevices: true + }) +}; diff --git a/libusb/tests/webusb-test-shim/package-lock.json b/libusb/tests/webusb-test-shim/package-lock.json new file mode 100644 index 0000000..813461c --- /dev/null +++ b/libusb/tests/webusb-test-shim/package-lock.json @@ -0,0 +1,50 @@ +{ + "name": "webusb-test-runner", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "webusb-test-runner", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "usb": "^2.11.0" + } + }, + "node_modules/@types/w3c-web-usb": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@types/w3c-web-usb/-/w3c-web-usb-1.0.10.tgz", + "integrity": "sha512-CHgUI5kTc/QLMP8hODUHhge0D4vx+9UiAwIGiT0sTy/B2XpdX1U5rJt6JSISgr6ikRT7vxV9EVAFeYZqUnl1gQ==" + }, + "node_modules/node-addon-api": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.0.0.tgz", + "integrity": "sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==" + }, + "node_modules/node-gyp-build": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.7.0.tgz", + "integrity": "sha512-PbZERfeFdrHQOOXiAKOY0VPbykZy90ndPKk0d+CFDegTKmWp1VgOTz2xACVbr1BjCWxrQp68CXtvNsveFhqDJg==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/usb": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/usb/-/usb-2.11.0.tgz", + "integrity": "sha512-u5+NZ6DtoW8TIBtuSArQGAZZ/K15i3lYvZBAYmcgI+RcDS9G50/KPrUd3CrU8M92ahyCvg5e0gc8BDvr5Hwejg==", + "hasInstallScript": true, + "dependencies": { + "@types/w3c-web-usb": "^1.0.6", + "node-addon-api": "^7.0.0", + "node-gyp-build": "^4.5.0" + }, + "engines": { + "node": ">=12.22.0 <13.0 || >=14.17.0" + } + } + } +} diff --git a/libusb/tests/webusb-test-shim/package.json b/libusb/tests/webusb-test-shim/package.json new file mode 100644 index 0000000..4e8fc54 --- /dev/null +++ b/libusb/tests/webusb-test-shim/package.json @@ -0,0 +1,10 @@ +{ + "name": "webusb-test-runner", + "private": true, + "license": "LGPL-2.1", + "main": "index.js", + "author": "Ingvar Stepanyan ", + "dependencies": { + "usb": "^2.11.0" + } +}