Merge "Upgrade libusb to v1.0.26" am: c8bf213799 am: 10e1fea9b2 am: 8e4a639761

Original change: https://android-review.googlesource.com/c/platform/external/libusb/+/2181617

Change-Id: I4fa41f9073eac217b9836908c7389b099881c5b8
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/.github/cifuzz.yml b/.github/cifuzz.yml
new file mode 100644
index 0000000..2a80713
--- /dev/null
+++ b/.github/cifuzz.yml
@@ -0,0 +1,26 @@
+name: CIFuzz
+on: [pull_request]
+jobs:
+  Fuzzing:
+    runs-on: ubuntu-latest
+    steps:
+    - name: Build Fuzzers
+      id: build
+      uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
+      with:
+        oss-fuzz-project-name: 'libusb'
+        dry-run: false
+        language: c
+    - name: Run Fuzzers
+      uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
+      with:
+        oss-fuzz-project-name: 'libusb'
+        fuzz-seconds: 600
+        dry-run: false
+        language: c
+    - name: Upload Crash
+      uses: actions/upload-artifact@v1
+      if: failure() && steps.build.outcome == 'success'
+      with:
+        name: artifacts
+        path: ./out/artifacts
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
new file mode 100644
index 0000000..ce5679e
--- /dev/null
+++ b/.github/workflows/linux.yml
@@ -0,0 +1,39 @@
+name: linux
+
+# Controls when the action will run. Triggers the workflow on push or pull request
+# events but only for the master branch
+on: [push, pull_request]
+
+# A workflow run is made up of one or more jobs that can run
+# sequentially or in parallel
+jobs:
+  # This workflow contains a single job called "build"
+  build:
+    runs-on: ubuntu-latest
+
+    # Steps represent a sequence of tasks that will be executed as part of the job
+    steps:
+      # Checks-out your repository under $GITHUB_WORKSPACE, so your job
+      # can access it
+      - uses: actions/checkout@v2
+
+      - 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
+
+      - name: udev
+        shell: bash
+        run: .private/ci-build.sh --build-dir build-udev -- --enable-udev
+
+      - name: umockdev test
+        run: .private/ci-container-build.sh docker.io/amd64/ubuntu:rolling
diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
new file mode 100644
index 0000000..6304b91
--- /dev/null
+++ b/.github/workflows/macos.yml
@@ -0,0 +1,36 @@
+name: macOS
+
+# Controls when the action will run. Triggers the workflow on push or pull request
+# events but only for the master branch
+on: [push, pull_request]
+
+# A workflow run is made up of one or more jobs that can run
+# sequentially or in parallel
+jobs:
+  # This workflow contains a single job called "build"
+  build:
+    runs-on: macos-latest
+
+    # Steps represent a sequence of tasks that will be executed as part of the job
+    steps:
+      # Checks-out your repository under $GITHUB_WORKSPACE, so your job
+      # can access it
+      - uses: actions/checkout@v2
+
+      - name: setup prerequisites
+        shell: bash
+        run: |
+          brew update
+          brew install autoconf automake libtool m4
+
+      - name: bootstrap
+        shell: bash
+        run: ./bootstrap.sh
+
+      - name: compile
+        shell: bash
+        run: .private/ci-build.sh --build-dir build
+
+      - name: Xcode
+        shell: bash
+        run: cd Xcode && xcodebuild -project libusb.xcodeproj
diff --git a/.github/workflows/msys2.yml b/.github/workflows/msys2.yml
new file mode 100644
index 0000000..116ad18
--- /dev/null
+++ b/.github/workflows/msys2.yml
@@ -0,0 +1,21 @@
+name: MSYS2 build
+on: [push, pull_request]
+
+jobs:
+  build:
+    runs-on: windows-latest
+    defaults:
+      run:
+        shell: msys2 {0}
+    steps:
+      - uses: actions/checkout@v2
+      - uses: msys2/setup-msys2@v2
+        with:
+          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
diff --git a/.private/ci-container-build.sh b/.private/ci-container-build.sh
new file mode 100755
index 0000000..ac8e707
--- /dev/null
+++ b/.private/ci-container-build.sh
@@ -0,0 +1,70 @@
+#!/bin/bash
+
+set -eu
+
+# keep container around if $DEBUG is set
+[ -n "${DEBUG:-}" ] || OPTS="--rm"
+
+if type podman >/dev/null 2>&1; then
+    RUNC=podman
+else
+    RUNC="sudo docker"
+fi
+
+MOUNT_MODE=":ro"
+
+$RUNC run --interactive ${RUNC_OPTIONS:-} ${OPTS:-} --volume `pwd`:/source${MOUNT_MODE:-} ${1:-docker.io/amd64/ubuntu:rolling} /bin/bash << EOF
+set -ex
+
+# avoid meson exit code 125; https://github.com/containers/podman/issues/11540
+trap '[ \$? -eq 0 ] || exit 1' EXIT
+
+# go-faster apt
+echo  'Acquire::Languages "none";' > /etc/apt/apt.conf.d/90nolanguages
+
+# upgrade
+export DEBIAN_FRONTEND=noninteractive
+apt-get update
+apt-get install -y eatmydata
+eatmydata apt-get -y --purge dist-upgrade
+
+# install build and test dependencies
+eatmydata apt-get install -y make libtool libudev-dev pkg-config umockdev libumockdev-dev
+
+# run build as user
+useradd build
+su -s /bin/bash - build << EOG
+set -ex
+
+mkdir "/tmp/builddir"
+cd "/tmp/builddir"
+
+CFLAGS="-O2"
+
+# enable extra warnings
+CFLAGS+=" -Winline"
+CFLAGS+=" -Wmissing-include-dirs"
+CFLAGS+=" -Wnested-externs"
+CFLAGS+=" -Wpointer-arith"
+CFLAGS+=" -Wredundant-decls"
+CFLAGS+=" -Wswitch-enum"
+export CFLAGS
+
+echo ""
+echo "Configuring ..."
+/source/configure --enable-examples-build --enable-tests-build
+
+echo ""
+echo "Building ..."
+make -j4 -k
+
+echo ""
+echo "Running umockdev tests ..."
+tests/umockdev
+
+echo "Running stress tests ..."
+tests/stress
+EOG
+EOF
+
+
diff --git a/.private/post-rewrite.sh b/.private/post-rewrite.sh
index 60ec3e4..d1f9999 100755
--- a/.private/post-rewrite.sh
+++ b/.private/post-rewrite.sh
@@ -6,7 +6,7 @@
 # .git/hooks/ with the following content:
 # #!/bin/sh
 # if [ -x .private/post-rewrite.sh ]; then
-#   source .private/post-rewrite.sh
+#   . .private/post-rewrite.sh
 # fi
 #
 # NOTE: These versioning hooks are intended to be used *INTERNALLY* by the
diff --git a/.private/pre-commit.sh b/.private/pre-commit.sh
index 48328f8..1c30c0f 100755
--- a/.private/pre-commit.sh
+++ b/.private/pre-commit.sh
@@ -8,7 +8,7 @@
 # .git/hooks/ with the following content:
 # #!/bin/sh
 # if [ -x .private/pre-commit.sh ]; then
-#   source .private/pre-commit.sh
+#   . .private/pre-commit.sh
 # fi
 #
 # NOTE: These versioning hooks are intended to be used *INTERNALLY* by the
diff --git a/.travis.yml b/.travis.yml
index 35326e3..e92f0c4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -47,7 +47,6 @@
             - automake
             - libtool
             - m4
-        update: true
 
 before_script:
     - ./bootstrap.sh
diff --git a/AUTHORS b/AUTHORS
index a366189..8f91512 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -12,6 +12,7 @@
 
 Other contributors:
 Aaron Luft
+Adam Korcz
 Adrian Bunk
 Adrien Destugues
 Akshay Jaggi
@@ -24,10 +25,12 @@
 Alex Vatchenko
 Andrew Aldridge
 Andrew Fernandes
+Andrew Goodney
 Andy Chunyu
 Andy McFadden
 Angus Gratton
 Anil Nair
+Ankur Verma
 Anthony Clay
 Antonio Ospite
 Artem Egorkine
@@ -38,21 +41,28 @@
 Bastien Nocera
 Bei Zhang
 Bence Csokas
+Benjamin Berg
 Benjamin Dobell
+Bohdan Tymkiv
 Brent Rector
+Bruno Harbulot
 Carl Karsten
-Chris Zhu
 Christophe Zeitouny
+Chris Zhu
 Chunyu Xie
 Colin Walters
+Craig Hutchinson
 Dave Camarillo
 David Engraf
 Davidlohr Bueso
 David Moore
 Dmitry Fleytman
 Dmitry Kostjuchenko
+Dmitry Zakablukov
 Doug Johnston
 Evan Hunter
+Evan Miller
+Fabrice Fontaine
 Federico Manzan
 Felipe Balbi
 Florian Albrechtskirchinger
@@ -60,10 +70,12 @@
 Francisco Facioni
 Frank Li
 Frederik Carlier
+Freek Dijkstra
 Gaurav Gupta
 Graeme Gill
 Greg Kroah-Hartman
 Gustavo Zacarias
+Haidong Zheng
 Hans Ulrich Niedermann
 Harry Mallon
 Hector Martin
@@ -76,6 +88,7 @@
 James Hanko
 Jeffrey Nichols
 Jie Zhang
+Jim Chen
 Johann Richard
 John Keeping
 John Sheu
@@ -86,35 +99,48 @@
 Joshua Blake
 Joshua Hou
 Juan Cruz Viotti
+Julian Scheel
 Justin Bischoff
 Karsten Koenig
+Keith Ahluwalia
 Kenjiro Tsuji
-KIMURA Masaru
+Kimura Masaru
 Konrad Rzepecki
 Kuangye Guo
 Lars Kanis
 Lars Wirzenius
 Lei Chen
 Léo Lam
+Liang Yunwang
 Luca Longinotti
+Luz Paz
+Mac Wang
+Marco Trevisan (Treviño)
 Marcus Meissner
+Mark Kuo
 Markus Heidelberg
 Martin Ettl
 Martin Koegler
+Martin Ling
 Martin Thierer
+Mathias Hjärtström
 Matthew Stapleton
 Matthias Bolte
+Michael Dickens
 Michel Zou
 Mike Frysinger
 Mikhail Gusarov
 Mikolaj Kucharski
 Morgan Leborgne
 Moritz Fischer
+Nancy Li
 Nia Alarie
 Nicholas Corgan
 Omri Iluz
 Orin Eman
+Ozkan Sezer
 Patrick Stewart
+Paul Cercueil
 Paul Fertser
 Paul Qureshi
 Pekka Nikander
@@ -123,12 +149,15 @@
 Rob Walker
 Romain Vimont
 Roman Kalashnikov
+Ryan Hileman
+Ryan Schmidt
 Saleem Rashid
 Sameeh Jubran
 Sean McBride
 Sebastian Pipping
 Sebastian von Ohr
 Sergey Serb
+Shawn Hoffman
 Simon Haggett
 Simon Newton
 Slash Gordon
@@ -157,13 +186,17 @@
 William Orr
 William Skellenger
 Xiaofan Chen
+Yegor Yefremov
 Zhiqiang Liu
 Zoltán Kovács
 Сергей Валерьевич
 Ларионов Даниил
 Роман Донченко
-liangyunwang
+jonner
+orbitcowboy
+osy
 parafin
 RipleyTom
+Seneral
 saur0n
 winterrace
diff --git a/ChangeLog b/ChangeLog
index df47bd5..326a9b3 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,33 @@
 For detailed information about the changes below, please see the git log or
 visit: http://log.libusb.info
 
+2022-04-10: v1.0.26
+* Fix regression with transfer free's after closing device
+* Fix regression with destroyed context if API is misused
+* Workaround for applications using missing default context
+* Fix hotplog enumeration regression
+* Fix Windows isochronous transfer regression since 1.0.24
+* Fix macOS exit crash in some multi-context cases
+* Build fixes for various platforms and configurations
+* Fix Windows HID multi-interface product string retrieval
+* Update isochronous OUT packet actual lengths on Windows
+* Add interface bound checking for broken devices
+* Add umockdev tests on Linux
+
+2022-01-31: v1.0.25
+* Linux: Fix regression with some particular devices
+* Linux: Fix regression with libusb_handle_events_timeout_completed()
+* Linux: Fix regression with cpu usage in libusb_bulk_transfer
+* Darwin (macOS): Add support for detaching kernel drivers with authorization.
+* Darwin (macOS): Do not drop partial data on timeout.
+* Darwin (macOS): Silence pipe error in set_interface_alt_setting().
+* Windows: Fix HID backend missing byte
+* Windows: Fix segfault with libusbk driver
+* Windows: Fix regression when using libusb0 driver
+* Windows: Support LIBUSB_TRANSFER_ADD_ZERO_PACKET on winusb
+* New NO_DEVICE_DISCOVERY option replaces WEAK_AUTHORITY option
+* Various other bug fixes and improvements
+
 2020-12-09: v1.0.24
 * Add new platform abstraction (#252)
 * Add Null POSIX backend
@@ -12,7 +39,7 @@
 * Darwin (macOS): use IOUSBDevice as darwin_device_class explicitly (#693)
 * Linux: Drop support for kernel older than 2.6.32
 * Linux: Provide an event thread name (#689)
-* Linux: Wait until all USBs have been reaped before freeing them (#607)
+* Linux: Wait until all URBs have been reaped before freeing them (#607)
 * NetBSD: Recognize device timeouts (#710)
 * OpenBSD: Allow opening ugen devices multiple times (#763)
 * OpenBSD: Support libusb_get_port_number() (#764)
@@ -61,7 +88,7 @@
 * Windows: Fix enumeration problems on Windows 8 and later
 * Windows: Major rework of poll() emulation
 * Windows: Numerous HID API fixes
-* Windows: Support cancelation of individual transfers (Vista and later)
+* Windows: Support cancellation of individual transfers (Vista and later)
 * Various other bug fixes and improvements
 
 2016-10-01: v1.0.21
diff --git a/METADATA b/METADATA
index 5c9b974..b1d55ae 100644
--- a/METADATA
+++ b/METADATA
@@ -9,11 +9,11 @@
     type: GIT
     value: "https://github.com/libusb/libusb"
   }
-  version: "v1.0.24"
+  version: "v1.0.26"
   license_type: RESTRICTED
   last_upgrade_date {
-    year: 2020
-    month: 12
+    year: 2022
+    month: 8
     day: 10
   }
 }
diff --git a/Xcode/libusb.xcodeproj/project.pbxproj b/Xcode/libusb.xcodeproj/project.pbxproj
index fcda7e2..759a102 100644
--- a/Xcode/libusb.xcodeproj/project.pbxproj
+++ b/Xcode/libusb.xcodeproj/project.pbxproj
@@ -56,7 +56,6 @@
 		008FC0211628BC5200BC5BE2 /* ezusb.c in Sources */ = {isa = PBXBuildFile; fileRef = 008FBFDC1628BA0E00BC5BE2 /* ezusb.c */; };
 		008FC0301628BC7400BC5BE2 /* listdevs.c in Sources */ = {isa = PBXBuildFile; fileRef = 008FBFE71628BA0E00BC5BE2 /* listdevs.c */; };
 		1438D77A17A2ED9F00166101 /* hotplug.c in Sources */ = {isa = PBXBuildFile; fileRef = 1438D77817A2ED9F00166101 /* hotplug.c */; };
-		1438D77B17A2ED9F00166101 /* hotplug.h in Headers */ = {isa = PBXBuildFile; fileRef = 1438D77917A2ED9F00166101 /* hotplug.h */; };
 		1438D77F17A2F0EA00166101 /* strerror.c in Sources */ = {isa = PBXBuildFile; fileRef = 1438D77E17A2F0EA00166101 /* strerror.c */; };
 		2018D95F24E453BA001589B2 /* events_posix.c in Sources */ = {isa = PBXBuildFile; fileRef = 2018D95E24E453BA001589B2 /* events_posix.c */; };
 		2018D96124E453D0001589B2 /* events_posix.h in Headers */ = {isa = PBXBuildFile; fileRef = 2018D96024E453D0001589B2 /* events_posix.h */; };
@@ -66,6 +65,32 @@
 		20951C0625630F8F00ED6351 /* ezusb.h in Headers */ = {isa = PBXBuildFile; fileRef = 008FBFDD1628BA0E00BC5BE2 /* ezusb.h */; };
 		20951C0F25630FD300ED6351 /* libusb_testlib.h in Headers */ = {isa = PBXBuildFile; fileRef = 008A23CA236C849A004854AA /* libusb_testlib.h */; };
 		20951C152563125200ED6351 /* libusb.h in Headers */ = {isa = PBXBuildFile; fileRef = 008FBF5A1628B7E800BC5BE2 /* libusb.h */; };
+		CEA0F5EF26321FAA00ADF3EC /* config.h in Headers */ = {isa = PBXBuildFile; fileRef = 008FBFA41628B84200BC5BE2 /* config.h */; };
+		CEA0F5F026321FAA00ADF3EC /* config.h in Headers */ = {isa = PBXBuildFile; fileRef = 008FBFA41628B84200BC5BE2 /* config.h */; };
+		CEA0F5F126321FAA00ADF3EC /* libusb.h in Headers */ = {isa = PBXBuildFile; fileRef = 008FBF5A1628B7E800BC5BE2 /* libusb.h */; };
+		CEA0F5F226321FAA00ADF3EC /* libusb-1.0.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 008FBF311628B79300BC5BE2 /* libusb-1.0.0.dylib */; };
+		CEA0F5F326321FAA00ADF3EC /* config.h in Headers */ = {isa = PBXBuildFile; fileRef = 008FBFA41628B84200BC5BE2 /* config.h */; };
+		CEA0F5F426321FAA00ADF3EC /* libusb.h in Headers */ = {isa = PBXBuildFile; fileRef = 008FBF5A1628B7E800BC5BE2 /* libusb.h */; };
+		CEA0F5F526321FAA00ADF3EC /* libusb-1.0.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 008FBF311628B79300BC5BE2 /* libusb-1.0.0.dylib */; };
+		CEA0F5F626321FAA00ADF3EC /* config.h in Headers */ = {isa = PBXBuildFile; fileRef = 008FBFA41628B84200BC5BE2 /* config.h */; };
+		CEA0F5F726321FAA00ADF3EC /* libusb.h in Headers */ = {isa = PBXBuildFile; fileRef = 008FBF5A1628B7E800BC5BE2 /* libusb.h */; };
+		CEA0F5F826321FAA00ADF3EC /* libusb-1.0.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 008FBF311628B79300BC5BE2 /* libusb-1.0.0.dylib */; };
+		CEA0F5F926321FAA00ADF3EC /* config.h in Headers */ = {isa = PBXBuildFile; fileRef = 008FBFA41628B84200BC5BE2 /* config.h */; };
+		CEA0F5FA26321FAA00ADF3EC /* libusb.h in Headers */ = {isa = PBXBuildFile; fileRef = 008FBF5A1628B7E800BC5BE2 /* libusb.h */; };
+		CEA0F5FB26321FAA00ADF3EC /* libusb-1.0.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 008FBF311628B79300BC5BE2 /* libusb-1.0.0.dylib */; };
+		CEA0F5FC26321FAA00ADF3EC /* config.h in Headers */ = {isa = PBXBuildFile; fileRef = 008FBFA41628B84200BC5BE2 /* config.h */; };
+		CEA0F5FD26321FAA00ADF3EC /* libusb.h in Headers */ = {isa = PBXBuildFile; fileRef = 008FBF5A1628B7E800BC5BE2 /* libusb.h */; };
+		CEA0F5FE26321FAA00ADF3EC /* libusb-1.0.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 008FBF311628B79300BC5BE2 /* libusb-1.0.0.dylib */; };
+		CEA0F5FF26321FAA00ADF3EC /* config.h in Headers */ = {isa = PBXBuildFile; fileRef = 008FBFA41628B84200BC5BE2 /* config.h */; };
+		CEA0F60026321FAA00ADF3EC /* libusb.h in Headers */ = {isa = PBXBuildFile; fileRef = 008FBF5A1628B7E800BC5BE2 /* libusb.h */; };
+		CEA0F60126321FAA00ADF3EC /* libusb-1.0.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 008FBF311628B79300BC5BE2 /* libusb-1.0.0.dylib */; };
+		CEA0F60226321FAA00ADF3EC /* config.h in Headers */ = {isa = PBXBuildFile; fileRef = 008FBFA41628B84200BC5BE2 /* config.h */; };
+		CEA0F60326321FAA00ADF3EC /* libusb.h in Headers */ = {isa = PBXBuildFile; fileRef = 008FBF5A1628B7E800BC5BE2 /* libusb.h */; };
+		CEA0F60426321FAA00ADF3EC /* libusb-1.0.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 008FBF311628B79300BC5BE2 /* libusb-1.0.0.dylib */; };
+		CEA0F60526321FAA00ADF3EC /* config.h in Headers */ = {isa = PBXBuildFile; fileRef = 008FBFA41628B84200BC5BE2 /* config.h */; };
+		CEA0F60626321FAA00ADF3EC /* libusb.h in Headers */ = {isa = PBXBuildFile; fileRef = 008FBF5A1628B7E800BC5BE2 /* libusb.h */; };
+		CEA0F60726321FAA00ADF3EC /* libusb-1.0.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 008FBF311628B79300BC5BE2 /* libusb-1.0.0.dylib */; };
+		CEA45DFB2634CDFA002FA97D /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEDCEA6E2632200A00F7AA49 /* Security.framework */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -240,7 +265,6 @@
 		008FC0151628BC0300BC5BE2 /* fxload */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = fxload; sourceTree = BUILT_PRODUCTS_DIR; };
 		008FC0261628BC6B00BC5BE2 /* listdevs */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = listdevs; sourceTree = BUILT_PRODUCTS_DIR; };
 		1438D77817A2ED9F00166101 /* hotplug.c */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = hotplug.c; sourceTree = "<group>"; tabWidth = 4; usesTabs = 1; };
-		1438D77917A2ED9F00166101 /* hotplug.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.h; path = hotplug.h; sourceTree = "<group>"; tabWidth = 4; usesTabs = 1; };
 		1438D77E17A2F0EA00166101 /* strerror.c */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = strerror.c; sourceTree = "<group>"; tabWidth = 4; usesTabs = 1; };
 		1443EE8416417E63007E0579 /* common.xcconfig */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = text.xcconfig; path = common.xcconfig; sourceTree = SOURCE_ROOT; tabWidth = 4; usesTabs = 1; };
 		1443EE8516417E63007E0579 /* debug.xcconfig */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = text.xcconfig; path = debug.xcconfig; sourceTree = SOURCE_ROOT; tabWidth = 4; usesTabs = 1; };
@@ -254,6 +278,7 @@
 		20468D6E243298C100650534 /* sam3u_benchmark.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sam3u_benchmark.c; sourceTree = "<group>"; usesTabs = 1; };
 		20468D75243298D300650534 /* testlibusb */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = testlibusb; sourceTree = BUILT_PRODUCTS_DIR; };
 		20468D7C2432990000650534 /* testlibusb.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = testlibusb.c; sourceTree = "<group>"; usesTabs = 1; };
+		CEDCEA6E2632200A00F7AA49 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -261,7 +286,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				006AD4261C8C5AD9007F8C6A /* libusb-1.0.0.dylib in Frameworks */,
+				CEA0F5F826321FAA00ADF3EC /* libusb-1.0.0.dylib in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -269,7 +294,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				006AD4261C8C5AD9007F8C6A /* libusb-1.0.0.dylib in Frameworks */,
+				CEA0F60126321FAA00ADF3EC /* libusb-1.0.0.dylib in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -279,6 +304,7 @@
 			files = (
 				008FBFAB1628B8CB00BC5BE2 /* libobjc.dylib in Frameworks */,
 				008FBFA91628B88000BC5BE2 /* IOKit.framework in Frameworks */,
+				CEA45DFB2634CDFA002FA97D /* Security.framework in Frameworks */,
 				008FBFA71628B87000BC5BE2 /* CoreFoundation.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -287,7 +313,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				006AD4261C8C5AD9007F8C6A /* libusb-1.0.0.dylib in Frameworks */,
+				CEA0F60726321FAA00ADF3EC /* libusb-1.0.0.dylib in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -303,7 +329,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				006AD4261C8C5AD9007F8C6A /* libusb-1.0.0.dylib in Frameworks */,
+				CEA0F5F226321FAA00ADF3EC /* libusb-1.0.0.dylib in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -311,7 +337,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				006AD4261C8C5AD9007F8C6A /* libusb-1.0.0.dylib in Frameworks */,
+				CEA0F5F526321FAA00ADF3EC /* libusb-1.0.0.dylib in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -319,7 +345,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				006AD4261C8C5AD9007F8C6A /* libusb-1.0.0.dylib in Frameworks */,
+				CEA0F5FB26321FAA00ADF3EC /* libusb-1.0.0.dylib in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -327,7 +353,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				006AD4261C8C5AD9007F8C6A /* libusb-1.0.0.dylib in Frameworks */,
+				CEA0F5FE26321FAA00ADF3EC /* libusb-1.0.0.dylib in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -335,7 +361,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				006AD4261C8C5AD9007F8C6A /* libusb-1.0.0.dylib in Frameworks */,
+				CEA0F60426321FAA00ADF3EC /* libusb-1.0.0.dylib in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -389,7 +415,6 @@
 				008FBF541628B7E800BC5BE2 /* core.c */,
 				008FBF551628B7E800BC5BE2 /* descriptor.c */,
 				1438D77817A2ED9F00166101 /* hotplug.c */,
-				1438D77917A2ED9F00166101 /* hotplug.h */,
 				008FBF561628B7E800BC5BE2 /* io.c */,
 				008FBF5A1628B7E800BC5BE2 /* libusb.h */,
 				008FBF671628B7E800BC5BE2 /* libusbi.h */,
@@ -453,6 +478,7 @@
 				008FBFAA1628B8CB00BC5BE2 /* libobjc.dylib */,
 				008FBFA81628B88000BC5BE2 /* IOKit.framework */,
 				008FBFA61628B87000BC5BE2 /* CoreFoundation.framework */,
+				CEDCEA6E2632200A00F7AA49 /* Security.framework */,
 			);
 			name = Apple;
 			path = ../libusb;
@@ -469,7 +495,6 @@
 				008FBFA51628B84200BC5BE2 /* config.h in Headers */,
 				008FBF931628B7E800BC5BE2 /* darwin_usb.h in Headers */,
 				2018D96124E453D0001589B2 /* events_posix.h in Headers */,
-				1438D77B17A2ED9F00166101 /* hotplug.h in Headers */,
 				008FBF901628B7E800BC5BE2 /* libusbi.h in Headers */,
 				008FBF9B1628B7E800BC5BE2 /* threads_posix.h in Headers */,
 				008FBFA11628B7E800BC5BE2 /* version.h in Headers */,
@@ -481,7 +506,7 @@
 			isa = PBXHeadersBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				008FBFA51628B84200BC5BE2 /* config.h in Headers */,
+				CEA0F5EF26321FAA00ADF3EC /* config.h in Headers */,
 				20951C152563125200ED6351 /* libusb.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -490,8 +515,8 @@
 			isa = PBXHeadersBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				008FBFA51628B84200BC5BE2 /* config.h in Headers */,
-				20951C152563125200ED6351 /* libusb.h in Headers */,
+				CEA0F5F026321FAA00ADF3EC /* config.h in Headers */,
+				CEA0F5F126321FAA00ADF3EC /* libusb.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -499,9 +524,9 @@
 			isa = PBXHeadersBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				008FBFA51628B84200BC5BE2 /* config.h in Headers */,
+				CEA0F5F326321FAA00ADF3EC /* config.h in Headers */,
 				20951C0625630F8F00ED6351 /* ezusb.h in Headers */,
-				20951C152563125200ED6351 /* libusb.h in Headers */,
+				CEA0F5F426321FAA00ADF3EC /* libusb.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -509,8 +534,8 @@
 			isa = PBXHeadersBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				008FBFA51628B84200BC5BE2 /* config.h in Headers */,
-				20951C152563125200ED6351 /* libusb.h in Headers */,
+				CEA0F5F626321FAA00ADF3EC /* config.h in Headers */,
+				CEA0F5F726321FAA00ADF3EC /* libusb.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -518,8 +543,8 @@
 			isa = PBXHeadersBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				008FBFA51628B84200BC5BE2 /* config.h in Headers */,
-				20951C152563125200ED6351 /* libusb.h in Headers */,
+				CEA0F5F926321FAA00ADF3EC /* config.h in Headers */,
+				CEA0F5FA26321FAA00ADF3EC /* libusb.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -527,8 +552,8 @@
 			isa = PBXHeadersBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				008FBFA51628B84200BC5BE2 /* config.h in Headers */,
-				20951C152563125200ED6351 /* libusb.h in Headers */,
+				CEA0F5FC26321FAA00ADF3EC /* config.h in Headers */,
+				CEA0F5FD26321FAA00ADF3EC /* libusb.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -536,9 +561,9 @@
 			isa = PBXHeadersBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				008FBFA51628B84200BC5BE2 /* config.h in Headers */,
+				CEA0F5FF26321FAA00ADF3EC /* config.h in Headers */,
 				20951C0F25630FD300ED6351 /* libusb_testlib.h in Headers */,
-				20951C152563125200ED6351 /* libusb.h in Headers */,
+				CEA0F60026321FAA00ADF3EC /* libusb.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -546,8 +571,8 @@
 			isa = PBXHeadersBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				008FBFA51628B84200BC5BE2 /* config.h in Headers */,
-				20951C152563125200ED6351 /* libusb.h in Headers */,
+				CEA0F60226321FAA00ADF3EC /* config.h in Headers */,
+				CEA0F60326321FAA00ADF3EC /* libusb.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -555,8 +580,8 @@
 			isa = PBXHeadersBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				008FBFA51628B84200BC5BE2 /* config.h in Headers */,
-				20951C152563125200ED6351 /* libusb.h in Headers */,
+				CEA0F60526321FAA00ADF3EC /* config.h in Headers */,
+				CEA0F60626321FAA00ADF3EC /* libusb.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
diff --git a/android/README b/android/README
index 32024ef..4af4c3d 100644
--- a/android/README
+++ b/android/README
@@ -48,69 +48,105 @@
 the package is built.
 
 
-For a rooted device it is possible to install libusb into the system
-image of a running device:
-
- 1. Enable ADB on the device.
-
- 2. Connect the device to a machine running ADB.
-
- 3. Execute the following commands on the machine
-    running ADB:
-
-    # Make the system partition writable
-    adb shell su -c "mount -o remount,rw /system"
-
-    # Install libusb
-    adb push obj/local/armeabi/libusb1.0.so /sdcard/
-    adb shell su -c "cat > /system/lib/libusb1.0.so < /sdcard/libusb1.0.so"
-    adb shell rm /sdcard/libusb1.0.so
-
-    # Install the samples and tests
-    for B in listdevs fxload xusb sam3u_benchmark hotplugtest stress
-    do
-      adb push "obj/local/armeabi/$B" /sdcard/
-      adb shell su -c "cat > /system/bin/$B < /sdcard/$B"
-      adb shell su -c "chmod 0755 /system/bin/$B"
-      adb shell rm "/sdcard/$B"
-    done
-
-    # Make the system partition read only again
-    adb shell su -c "mount -o remount,ro /system"
-
-    # Run listdevs to
-    adb shell su -c "listdevs"
-
- 4. If your device only has a single OTG port then ADB can generally
-    be switched to using Wifi with the following commands when connected
-    via USB:
-
-    adb shell netcfg
-    # Note the wifi IP address of the phone
-    adb tcpip 5555
-    # Use the IP address from netcfg
-    adb connect 192.168.1.123:5555
-
 Runtime Permissions:
 --------------------
 
-The default system configuration on most Android device will not allow
-access to USB devices. There are several options for changing this.
+The Runtime Permissions on Android can be transferred from Java to Native
+over the following approach:
 
-If you have control of the system image then you can modify the
-ueventd.rc used in the image to change the permissions on
-/dev/bus/usb/*/*. If using this approach then it is advisable to
-create a new Android permission to protect access to these files.
-It is not advisable to give all applications read and write permissions
-to these files.
+  JAVA:
 
-For rooted devices the code using libusb could be executed as root
-using the "su" command. An alternative would be to use the "su" command
-to change the permissions on the appropriate /dev/bus/usb/ files.
+   --> Obtain USB permissions over the android.hardware.usb.UsbManager class
 
-Users have reported success in using android.hardware.usb.UsbManager
-to request permission to use the UsbDevice and then opening the
-device. The difficulties in this method is that there is no guarantee
-that it will continue to work in the future Android versions, it
-requires invoking Java APIs and running code to match each
-android.hardware.usb.UsbDevice to a libusb_device.
+      usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
+      HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
+      for (UsbDevice usbDevice : deviceList.values()) {
+          usbManager.requestPermission(usbDevice, mPermissionIntent);
+      }
+
+   --> Get the native FileDescriptor of the UsbDevice and transfer it to
+       Native over JNI or JNA
+
+      UsbDeviceConnection usbDeviceConnection = usbManager.openDevice(camDevice);
+      int fileDescriptor = usbDeviceConnection.getFileDescriptor();
+
+   --> JNA sample method:
+
+      JNA.INSTANCE.set_the_native_Descriptor(fileDescriptor);
+
+  NATIVE:
+
+   --> Initialize libusb on Android
+
+      set_the_native_Descriptor(int fileDescriptor) {
+          libusb_context *ctx;
+          libusb_device_handle *devh;
+          libusb_set_option(&ctx, LIBUSB_OPTION_NO_DEVICE_DISCOVERY, NULL);
+          libusb_init(&ctx);
+          libusb_wrap_sys_device(NULL, (intptr_t)fileDescriptor, &devh);
+      }
+      /* From this point you can regularly use all libusb functions as usual */
+
+   About LIBUSB_OPTION_NO_DEVICE_DISCOVERY:
+
+    The method libusb_set_option(&ctx, LIBUSB_OPTION_NO_DEVICE_DISCOVERY, NULL)
+    does not affect the ctx.
+    It allows initializing libusb on unrooted Android devices by skipping
+    the device enumeration.
+
+Rooted Devices:
+---------------
+
+    For rooted devices the code using libusb could be executed as root
+    using the "su" command. An alternative would be to use the "su" command
+    to change the permissions on the appropriate /dev/bus/usb/ files.
+
+    Users have reported success in using android.hardware.usb.UsbManager
+    to request permission to use the UsbDevice and then opening the
+    device. The difficulties in this method is that there is no guarantee
+    that it will continue to work in the future Android versions, it
+    requires invoking Java APIs and running code to match each
+    android.hardware.usb.UsbDevice to a libusb_device.
+
+    For a rooted device it is possible to install libusb into the system
+    image of a running device:
+
+     1. Enable ADB on the device.
+
+     2. Connect the device to a machine running ADB.
+
+     3. Execute the following commands on the machine
+        running ADB:
+
+        # Make the system partition writable
+        adb shell su -c "mount -o remount,rw /system"
+
+        # Install libusb
+        adb push obj/local/armeabi/libusb1.0.so /sdcard/
+        adb shell su -c "cat > /system/lib/libusb1.0.so < /sdcard/libusb1.0.so"
+        adb shell rm /sdcard/libusb1.0.so
+
+        # Install the samples and tests
+        for B in listdevs fxload xusb sam3u_benchmark hotplugtest stress
+        do
+          adb push "obj/local/armeabi/$B" /sdcard/
+          adb shell su -c "cat > /system/bin/$B < /sdcard/$B"
+          adb shell su -c "chmod 0755 /system/bin/$B"
+          adb shell rm "/sdcard/$B"
+        done
+
+        # Make the system partition read only again
+        adb shell su -c "mount -o remount,ro /system"
+
+        # Run listdevs to
+        adb shell su -c "listdevs"
+
+     4. If your device only has a single OTG port then ADB can generally
+        be switched to using Wifi with the following commands when connected
+        via USB:
+
+        adb shell netcfg
+        # Note the wifi IP address of the phone
+        adb tcpip 5555
+        # Use the IP address from netcfg
+        adb connect 192.168.1.123:5555
diff --git a/android/examples/unrooted_android.c b/android/examples/unrooted_android.c
new file mode 100644
index 0000000..33793c7
--- /dev/null
+++ b/android/examples/unrooted_android.c
@@ -0,0 +1,300 @@
+/*
+ *  libusb example program for reading out USB descriptors on unrooted Android
+ *  (based on testlibusb.c)
+ *
+ *  Copyright 2020-2021 Peter Stoiber
+ *
+ *  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
+ *
+ *  Please contact the author if you need another license.
+ *  This Repository is provided "as is", without warranties of any kind.
+*/
+
+/*
+ * This example creates a shared object which can be accessed over JNA or JNI from Java or Kotlin in Android.
+ * Hint: If you are using Android Studio, set the "Debug type" to "Java Only" to receive debug messages.
+ */
+
+/*
+ * Usage:
+ * First, you have to connect your USB device from the Java side.
+ * Use the android.hardware.usb class to find the USB device, claim the interfaces, and open the usb_device_connection
+ * Obtain the native File Descriptor --> usb_device_connection.getFileDescriptor()
+ * Pass the received int value to the unrooted_usb_description method of this code (over JNA)
+ */
+
+/*
+ * libusb can only be included in Android projects using NDK for now. (CMake is not supported at the moment)
+ * Clone the libusb git repo into your Android project and include the Android.mk file in your build.gradle.
+ */
+
+/*
+ Example JNA Approach:
+    public interface unrooted_sample extends Library {
+        public static final unrooted_sample INSTANCE = Native.load("unrooted_android", unrooted_sample.class);
+        public int unrooted_usb_description (int fileDescriptor);
+    }
+    unrooted_sample.INSTANCE.unrooted_usb_description( usbDeviceConnection.getFileDescriptor());
+ */
+
+#include <jni.h>
+#include <string.h>
+#include "unrooted_android.h"
+#include "libusb.h"
+#include <android/log.h>
+#define  LOG_TAG    "LibUsb"
+#define  LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
+
+int verbose = 0;
+
+static void print_endpoint_comp(const struct libusb_ss_endpoint_companion_descriptor *ep_comp)
+{
+    LOGD("      USB 3.0 Endpoint Companion:\n");
+    LOGD("        bMaxBurst:           %u\n", ep_comp->bMaxBurst);
+    LOGD("        bmAttributes:        %02xh\n", ep_comp->bmAttributes);
+    LOGD("        wBytesPerInterval:   %u\n", ep_comp->wBytesPerInterval);
+}
+
+static void print_endpoint(const struct libusb_endpoint_descriptor *endpoint)
+{
+    int i, ret;
+
+    LOGD("      Endpoint:\n");
+    LOGD("        bEndpointAddress:    %02xh\n", endpoint->bEndpointAddress);
+    LOGD("        bmAttributes:        %02xh\n", endpoint->bmAttributes);
+    LOGD("        wMaxPacketSize:      %u\n", endpoint->wMaxPacketSize);
+    LOGD("        bInterval:           %u\n", endpoint->bInterval);
+    LOGD("        bRefresh:            %u\n", endpoint->bRefresh);
+    LOGD("        bSynchAddress:       %u\n", endpoint->bSynchAddress);
+
+    for (i = 0; i < endpoint->extra_length;) {
+        if (LIBUSB_DT_SS_ENDPOINT_COMPANION == endpoint->extra[i + 1]) {
+            struct libusb_ss_endpoint_companion_descriptor *ep_comp;
+
+            ret = libusb_get_ss_endpoint_companion_descriptor(NULL, endpoint, &ep_comp);
+            if (LIBUSB_SUCCESS != ret)
+                continue;
+
+            print_endpoint_comp(ep_comp);
+
+            libusb_free_ss_endpoint_companion_descriptor(ep_comp);
+        }
+
+        i += endpoint->extra[i];
+    }
+}
+
+static void print_altsetting(const struct libusb_interface_descriptor *interface)
+{
+    uint8_t i;
+
+    LOGD("    Interface:\n");
+    LOGD("      bInterfaceNumber:      %u\n", interface->bInterfaceNumber);
+    LOGD("      bAlternateSetting:     %u\n", interface->bAlternateSetting);
+    LOGD("      bNumEndpoints:         %u\n", interface->bNumEndpoints);
+    LOGD("      bInterfaceClass:       %u\n", interface->bInterfaceClass);
+    LOGD("      bInterfaceSubClass:    %u\n", interface->bInterfaceSubClass);
+    LOGD("      bInterfaceProtocol:    %u\n", interface->bInterfaceProtocol);
+    LOGD("      iInterface:            %u\n", interface->iInterface);
+
+    for (i = 0; i < interface->bNumEndpoints; i++)
+        print_endpoint(&interface->endpoint[i]);
+}
+
+static void print_2_0_ext_cap(struct libusb_usb_2_0_extension_descriptor *usb_2_0_ext_cap)
+{
+    LOGD("    USB 2.0 Extension Capabilities:\n");
+    LOGD("      bDevCapabilityType:    %u\n", usb_2_0_ext_cap->bDevCapabilityType);
+    LOGD("      bmAttributes:          %08xh\n", usb_2_0_ext_cap->bmAttributes);
+}
+
+static void print_ss_usb_cap(struct libusb_ss_usb_device_capability_descriptor *ss_usb_cap)
+{
+    LOGD("    USB 3.0 Capabilities:\n");
+    LOGD("      bDevCapabilityType:    %u\n", ss_usb_cap->bDevCapabilityType);
+    LOGD("      bmAttributes:          %02xh\n", ss_usb_cap->bmAttributes);
+    LOGD("      wSpeedSupported:       %u\n", ss_usb_cap->wSpeedSupported);
+    LOGD("      bFunctionalitySupport: %u\n", ss_usb_cap->bFunctionalitySupport);
+    LOGD("      bU1devExitLat:         %u\n", ss_usb_cap->bU1DevExitLat);
+    LOGD("      bU2devExitLat:         %u\n", ss_usb_cap->bU2DevExitLat);
+}
+
+static void print_bos(libusb_device_handle *handle)
+{
+    struct libusb_bos_descriptor *bos;
+    uint8_t i;
+    int ret;
+
+    ret = libusb_get_bos_descriptor(handle, &bos);
+    if (ret < 0)
+        return;
+
+    LOGD("  Binary Object Store (BOS):\n");
+    LOGD("    wTotalLength:            %u\n", bos->wTotalLength);
+    LOGD("    bNumDeviceCaps:          %u\n", bos->bNumDeviceCaps);
+
+    for (i = 0; i < bos->bNumDeviceCaps; i++) {
+        struct libusb_bos_dev_capability_descriptor *dev_cap = bos->dev_capability[i];
+
+        if (dev_cap->bDevCapabilityType == LIBUSB_BT_USB_2_0_EXTENSION) {
+            struct libusb_usb_2_0_extension_descriptor *usb_2_0_extension;
+
+            ret = libusb_get_usb_2_0_extension_descriptor(NULL, dev_cap, &usb_2_0_extension);
+            if (ret < 0)
+                return;
+
+            print_2_0_ext_cap(usb_2_0_extension);
+            libusb_free_usb_2_0_extension_descriptor(usb_2_0_extension);
+        } else if (dev_cap->bDevCapabilityType == LIBUSB_BT_SS_USB_DEVICE_CAPABILITY) {
+            struct libusb_ss_usb_device_capability_descriptor *ss_dev_cap;
+
+            ret = libusb_get_ss_usb_device_capability_descriptor(NULL, dev_cap, &ss_dev_cap);
+            if (ret < 0)
+                return;
+
+            print_ss_usb_cap(ss_dev_cap);
+            libusb_free_ss_usb_device_capability_descriptor(ss_dev_cap);
+        }
+    }
+
+    libusb_free_bos_descriptor(bos);
+}
+
+static void print_interface(const struct libusb_interface *interface)
+{
+    int i;
+
+    for (i = 0; i < interface->num_altsetting; i++)
+        print_altsetting(&interface->altsetting[i]);
+}
+
+static void print_configuration(struct libusb_config_descriptor *config)
+{
+    uint8_t i;
+
+    LOGD("  Configuration:\n");
+    LOGD("    wTotalLength:            %u\n", config->wTotalLength);
+    LOGD("    bNumInterfaces:          %u\n", config->bNumInterfaces);
+    LOGD("    bConfigurationValue:     %u\n", config->bConfigurationValue);
+    LOGD("    iConfiguration:          %u\n", config->iConfiguration);
+    LOGD("    bmAttributes:            %02xh\n", config->bmAttributes);
+    LOGD("    MaxPower:                %u\n", config->MaxPower);
+
+    for (i = 0; i < config->bNumInterfaces; i++)
+        print_interface(&config->interface[i]);
+}
+
+static void print_device(libusb_device *dev, libusb_device_handle *handle)
+{
+    struct libusb_device_descriptor desc;
+    unsigned char string[256];
+    const char *speed;
+    int ret;
+    uint8_t i;
+
+    switch (libusb_get_device_speed(dev)) {
+        case LIBUSB_SPEED_LOW:		speed = "1.5M"; break;
+        case LIBUSB_SPEED_FULL:		speed = "12M"; break;
+        case LIBUSB_SPEED_HIGH:		speed = "480M"; break;
+        case LIBUSB_SPEED_SUPER:	speed = "5G"; break;
+        case LIBUSB_SPEED_SUPER_PLUS:	speed = "10G"; break;
+        default:			speed = "Unknown";
+    }
+
+    ret = libusb_get_device_descriptor(dev, &desc);
+    if (ret < 0) {
+        LOGD("failed to get device descriptor");
+        return;
+    }
+
+    LOGD("Dev (bus %u, device %u): %04X - %04X speed: %s\n",
+           libusb_get_bus_number(dev), libusb_get_device_address(dev),
+           desc.idVendor, desc.idProduct, speed);
+
+    if (!handle)
+        libusb_open(dev, &handle);
+
+    if (handle) {
+        if (desc.iManufacturer) {
+            ret = libusb_get_string_descriptor_ascii(handle, desc.iManufacturer, string, sizeof(string));
+            if (ret > 0)
+                LOGD("  Manufacturer:              %s\n", (char *)string);
+        }
+
+        if (desc.iProduct) {
+            ret = libusb_get_string_descriptor_ascii(handle, desc.iProduct, string, sizeof(string));
+            if (ret > 0)
+                LOGD("  Product:                   %s\n", (char *)string);
+        }
+
+        if (desc.iSerialNumber && verbose) {
+            ret = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, string, sizeof(string));
+            if (ret > 0)
+                LOGD("  Serial Number:             %s\n", (char *)string);
+        }
+    }
+
+    if (verbose) {
+        for (i = 0; i < desc.bNumConfigurations; i++) {
+            struct libusb_config_descriptor *config;
+
+            ret = libusb_get_config_descriptor(dev, i, &config);
+            if (LIBUSB_SUCCESS != ret) {
+                LOGD("  Couldn't retrieve descriptors\n");
+                continue;
+            }
+
+            print_configuration(config);
+
+            libusb_free_config_descriptor(config);
+        }
+
+        if (handle && desc.bcdUSB >= 0x0201)
+            print_bos(handle);
+    }
+
+    if (handle)
+        libusb_close(handle);
+}
+
+
+/* fileDescriptor = the native file descriptor obtained in Java and transferred to native over JNA, for example */
+int unrooted_usb_description(int fileDescriptor)
+{
+    libusb_context *ctx = NULL;
+    libusb_device_handle *devh = NULL;
+    int r = 0;
+    verbose = 1;
+    r = libusb_set_option(NULL, LIBUSB_OPTION_NO_DEVICE_DISCOVERY, NULL);
+    if (r != LIBUSB_SUCCESS) {
+        LOGD("libusb_set_option failed: %d\n", r);
+        return -1;
+    }
+    r = libusb_init(&ctx);
+    if (r < 0) {
+        LOGD("libusb_init failed: %d\n", r);
+        return r;
+    }
+    r = libusb_wrap_sys_device(ctx, (intptr_t)fileDescriptor, &devh);
+    if (r < 0) {
+        LOGD("libusb_wrap_sys_device failed: %d\n", r);
+        return r;
+    } else if (devh == NULL) {
+        LOGD("libusb_wrap_sys_device returned invalid handle\n");
+        return r;
+    }
+    print_device(libusb_get_device(devh), devh);
+    return r;
+}
diff --git a/android/examples/unrooted_android.h b/android/examples/unrooted_android.h
new file mode 100644
index 0000000..7ccd408
--- /dev/null
+++ b/android/examples/unrooted_android.h
@@ -0,0 +1,36 @@
+/*
+ *  Copyright 2021 Peter Stoiber
+ *
+ *  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
+ *
+ *  Please contact the author if you need another license.
+ *  This Repository is provided "as is", without warranties of any kind.
+*/
+
+#ifndef unrooted_android_H
+#define unrooted_android_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int unrooted_usb_description(int fileDescriptor);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
diff --git a/android/jni/examples.mk b/android/jni/examples.mk
index d0ffb82..4a4c068 100644
--- a/android/jni/examples.mk
+++ b/android/jni/examples.mk
@@ -20,6 +20,12 @@
 LIBUSB_ROOT_REL := ../..
 LIBUSB_ROOT_ABS := $(LOCAL_PATH)/../..
 
+ifeq ($(USE_PC_NAME),1)
+  LIBUSB_MODULE := usb-1.0
+else
+  LIBUSB_MODULE := libusb1.0
+endif
+
 # dpfp
 
 include $(CLEAR_VARS)
@@ -31,7 +37,7 @@
   $(LOCAL_PATH)/.. \
   $(LIBUSB_ROOT_ABS)
 
-LOCAL_SHARED_LIBRARIES += libusb1.0
+LOCAL_SHARED_LIBRARIES += $(LIBUSB_MODULE)
 
 LOCAL_MODULE := dpfp
 LOCAL_LICENSE_KINDS := SPDX-license-identifier-LGPL SPDX-license-identifier-LGPL-2.1 SPDX-license-identifier-LGPL-3.0
@@ -53,7 +59,7 @@
 
 LOCAL_CFLAGS := -DDPFP_THREADED -pthread
 
-LOCAL_SHARED_LIBRARIES += libusb1.0
+LOCAL_SHARED_LIBRARIES += $(LIBUSB_MODULE)
 
 LOCAL_MODULE := dpfp_threaded
 LOCAL_LICENSE_KINDS := SPDX-license-identifier-LGPL SPDX-license-identifier-LGPL-2.1 SPDX-license-identifier-LGPL-3.0
@@ -74,7 +80,7 @@
   $(LOCAL_PATH)/.. \
   $(LIBUSB_ROOT_ABS)
 
-LOCAL_SHARED_LIBRARIES += libusb1.0
+LOCAL_SHARED_LIBRARIES += $(LIBUSB_MODULE)
 
 LOCAL_MODULE := fxload
 LOCAL_LICENSE_KINDS := SPDX-license-identifier-LGPL SPDX-license-identifier-LGPL-2.1 SPDX-license-identifier-LGPL-3.0
@@ -94,7 +100,7 @@
   $(LOCAL_PATH)/.. \
   $(LIBUSB_ROOT_ABS)
 
-LOCAL_SHARED_LIBRARIES += libusb1.0
+LOCAL_SHARED_LIBRARIES += $(LIBUSB_MODULE)
 
 LOCAL_MODULE := hotplugtest
 LOCAL_LICENSE_KINDS := SPDX-license-identifier-LGPL SPDX-license-identifier-LGPL-2.1 SPDX-license-identifier-LGPL-3.0
@@ -114,7 +120,7 @@
   $(LOCAL_PATH)/.. \
   $(LIBUSB_ROOT_ABS)
 
-LOCAL_SHARED_LIBRARIES += libusb1.0
+LOCAL_SHARED_LIBRARIES += $(LIBUSB_MODULE)
 
 LOCAL_MODULE := listdevs
 LOCAL_LICENSE_KINDS := SPDX-license-identifier-LGPL SPDX-license-identifier-LGPL-2.1 SPDX-license-identifier-LGPL-3.0
@@ -134,7 +140,7 @@
   $(LOCAL_PATH)/.. \
   $(LIBUSB_ROOT_ABS)
 
-LOCAL_SHARED_LIBRARIES += libusb1.0
+LOCAL_SHARED_LIBRARIES += $(LIBUSB_MODULE)
 
 LOCAL_MODULE := sam3u_benchmark
 LOCAL_LICENSE_KINDS := SPDX-license-identifier-LGPL SPDX-license-identifier-LGPL-2.1 SPDX-license-identifier-LGPL-3.0
@@ -154,7 +160,7 @@
   $(LOCAL_PATH)/.. \
   $(LIBUSB_ROOT_ABS)
 
-LOCAL_SHARED_LIBRARIES += libusb1.0
+LOCAL_SHARED_LIBRARIES += $(LIBUSB_MODULE)
 
 LOCAL_MODULE := xusb
 LOCAL_LICENSE_KINDS := SPDX-license-identifier-LGPL SPDX-license-identifier-LGPL-2.1 SPDX-license-identifier-LGPL-3.0
@@ -162,3 +168,22 @@
 LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../COPYING $(LOCAL_PATH)/../../NOTICE
 
 include $(BUILD_EXECUTABLE)
+
+# unrooted_android
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+  $(LIBUSB_ROOT_REL)/android/examples/unrooted_android.c
+
+LOCAL_C_INCLUDES += \
+  $(LOCAL_PATH)/.. \
+  $(LIBUSB_ROOT_ABS)
+
+LOCAL_SHARED_LIBRARIES += libusb1.0
+
+LOCAL_LDLIBS += -llog
+
+LOCAL_MODULE := unrooted_android
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/android/jni/libusb.mk b/android/jni/libusb.mk
index c7ac77c..d40c88a 100644
--- a/android/jni/libusb.mk
+++ b/android/jni/libusb.mk
@@ -48,7 +48,15 @@
 
 LOCAL_LDLIBS := -llog
 
-LOCAL_MODULE := libusb1.0
+ifeq ($(USE_PC_NAME),1)
+  LOCAL_MODULE := usb-1.0
+else
+  LOCAL_MODULE := libusb1.0
+  $(warning Building to legacy library name libusb1.0, which differs from pkg-config.)
+  $(warning Use ndk-build USE_PC_NAME=1 to change the module name to the compatible usb-1.0.)
+  $(warning USE_PC_NAME=1 may be the default in the future.)
+endif
+
 LOCAL_LICENSE_KINDS := SPDX-license-identifier-LGPL SPDX-license-identifier-LGPL-2.1 SPDX-license-identifier-LGPL-3.0
 LOCAL_LICENSE_CONDITIONS := restricted
 LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../COPYING $(LOCAL_PATH)/../../NOTICE
diff --git a/android/jni/tests.mk b/android/jni/tests.mk
index 56315c5..8d7a04b 100644
--- a/android/jni/tests.mk
+++ b/android/jni/tests.mk
@@ -20,6 +20,12 @@
 LIBUSB_ROOT_REL := ../..
 LIBUSB_ROOT_ABS := $(LOCAL_PATH)/../..
 
+ifeq ($(USE_PC_NAME),1)
+  LIBUSB_MODULE := usb-1.0
+else
+  LIBUSB_MODULE := libusb1.0
+endif
+
 # stress
 
 include $(CLEAR_VARS)
@@ -32,7 +38,7 @@
   $(LOCAL_PATH)/.. \
   $(LIBUSB_ROOT_ABS)
 
-LOCAL_SHARED_LIBRARIES += libusb1.0
+LOCAL_SHARED_LIBRARIES += $(LIBUSB_MODULE)
 
 LOCAL_MODULE := stress
 LOCAL_LICENSE_KINDS := SPDX-license-identifier-LGPL SPDX-license-identifier-LGPL-2.1 SPDX-license-identifier-LGPL-3.0
diff --git a/appveyor.yml b/appveyor.yml
index 36cfa96..c70f3eb 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -42,6 +42,8 @@
       - cmd: msbuild "%APPVEYOR_BUILD_FOLDER%\msvc\libusb_2015.sln" /m /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
       - cmd: C:\msys64\usr\bin\bash -l "%APPVEYOR_BUILD_FOLDER%\.private\appveyor_build.sh" MinGW
       - cmd: C:\cygwin\bin\bash -l "%APPVEYOR_BUILD_FOLDER%\.private\appveyor_build.sh" cygwin
+    after_build:
+      - cmd: 7z a "libusb-build_%APPVEYOR_BUILD_WORKER_IMAGE%_%PLATFORM%_%CONFIGURATION%.7z" tag_* README-build.txt %PLATFORM%\%CONFIGURATION%\dll\*.* %PLATFORM%\%CONFIGURATION%\lib\*.* %PLATFORM%\%CONFIGURATION%\examples\*.exe %PLATFORM%\%CONFIGURATION%\tests\*.exe C:\msys64\home\appveyor\libusb-MinGW-Win32 C:\cygwin\home\appveyor\libusb-cygwin-Win32
 
   -
     matrix:
@@ -56,6 +58,8 @@
       - cmd: msbuild "%APPVEYOR_BUILD_FOLDER%\msvc\libusb_2015.sln" /m /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
       - cmd: C:\msys64\usr\bin\bash -l "%APPVEYOR_BUILD_FOLDER%\.private\appveyor_build.sh" MinGW
       - cmd: C:\cygwin64\bin\bash -l "%APPVEYOR_BUILD_FOLDER%\.private\appveyor_build.sh" cygwin
+    after_build:
+      - cmd: 7z a "libusb-build_%APPVEYOR_BUILD_WORKER_IMAGE%_%PLATFORM%_%CONFIGURATION%.7z" tag_* README-build.txt %PLATFORM%\%CONFIGURATION%\dll\*.* %PLATFORM%\%CONFIGURATION%\lib\*.* %PLATFORM%\%CONFIGURATION%\examples\*.exe %PLATFORM%\%CONFIGURATION%\tests\*.exe C:\msys64\home\appveyor\libusb-MinGW-x64 C:\cygwin64\home\appveyor\libusb-cygwin-x64
 
   -
     matrix:
@@ -70,3 +74,11 @@
         - image: Visual Studio 2019
     build:
       project: msvc\libusb_2019.sln
+
+after_build:
+  - cmd: ECHO This was built by %APPVEYOR_BUILD_WORKER_IMAGE% from %APPVEYOR_REPO_NAME% commit %APPVEYOR_REPO_COMMIT% > README-build.txt
+  - cmd: ECHO > tag_%APPVEYOR_REPO_TAG_NAME%_commit_%APPVEYOR_REPO_COMMIT%
+  - cmd: 7z a "libusb-build_%APPVEYOR_BUILD_WORKER_IMAGE%_%PLATFORM%_%CONFIGURATION%.7z" tag_* README-build.txt %PLATFORM%\%CONFIGURATION%\dll\*.* %PLATFORM%\%CONFIGURATION%\lib\*.* %PLATFORM%\%CONFIGURATION%\examples\*.exe %PLATFORM%\%CONFIGURATION%\tests\*.exe
+
+artifacts:
+  - path: "libusb-build_%APPVEYOR_BUILD_WORKER_IMAGE%_%PLATFORM%_%CONFIGURATION%.7z"
diff --git a/autogen.sh b/autogen.sh
index 62d68e5..2ffb10e 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -2,7 +2,9 @@
 
 set -e
 
-./bootstrap.sh
+srcdir="$(dirname "$0")"
+
+"$srcdir"/bootstrap.sh
 if [ -z "$NOCONFIGURE" ]; then
-    exec ./configure --enable-examples-build --enable-tests-build "$@"
+    exec "$srcdir"/configure --enable-examples-build --enable-tests-build "$@"
 fi
diff --git a/bootstrap.sh b/bootstrap.sh
index cfd2b45..fc555d5 100755
--- a/bootstrap.sh
+++ b/bootstrap.sh
@@ -2,6 +2,8 @@
 
 set -e
 
+cd "$(dirname "$0")"
+
 if [ ! -d m4 ]; then
     mkdir m4
 fi
diff --git a/configure.ac b/configure.ac
index 5b880f6..d4f1251 100644
--- a/configure.ac
+++ b/configure.ac
@@ -124,6 +124,7 @@
 	platform=windows
 	test "x$enable_shared" = xyes && create_import_lib=yes
 	EXTRA_CFLAGS="-mwin32 -fno-omit-frame-pointer"
+	EXTRA_LDFLAGS="-static-libgcc"
 	;;
 *)
 	AC_MSG_RESULT([Null])
@@ -152,6 +153,7 @@
 	AC_SEARCH_LIBS([pthread_create], [pthread],
 		[test "x$ac_cv_search_pthread_create" != "xnone required" && AC_SUBST(THREAD_LIBS, [-lpthread])],
 		[], [])
+	AC_SEARCH_LIBS([__atomic_fetch_add_4], [atomic])
 elif test "x$platform" = xwindows; then
 	AC_DEFINE([PLATFORM_WINDOWS], [1], [Define to 1 if compiling for a Windows platform.])
 else
@@ -161,7 +163,8 @@
 case $backend in
 darwin)
 	AC_CHECK_FUNCS([pthread_threadid_np])
-	LIBS="${LIBS} -lobjc -Wl,-framework,IOKit -Wl,-framework,CoreFoundation"
+	LIBS="${LIBS} -lobjc -Wl,-framework,IOKit -Wl,-framework,CoreFoundation -Wl,-framework,Security"
+	AC_CHECK_HEADERS([IOKit/usb/IOUSBHostFamilyDefinitions.h])
 	;;
 haiku)
 	LIBS="${LIBS} -lbe"
@@ -170,12 +173,20 @@
 	AC_SEARCH_LIBS([clock_gettime], [rt], [], [], [])
 	AC_CHECK_FUNCS([pthread_setname_np])
 	AC_ARG_ENABLE([udev],
-		[AC_HELP_STRING([--enable-udev], [use udev for device enumeration and hotplug support (recommended) [default=yes]])],
+		[AS_HELP_STRING([--enable-udev], [use udev for device enumeration and hotplug support (recommended) [default=yes]])],
 		[use_udev=$enableval], [use_udev=yes])
 	if test "x$use_udev" = xyes; then
 		dnl system has udev. use it or fail!
 		AC_CHECK_HEADER([libudev.h], [], [AC_MSG_ERROR([udev support requested but libudev header not installed])])
 		AC_CHECK_LIB([udev], [udev_new], [], [AC_MSG_ERROR([udev support requested but libudev not installed])])
+
+		# We can build umockdev tests (if available)
+		PKG_PROG_PKG_CONFIG
+		PKG_CHECK_MODULES(UMOCKDEV, umockdev-1.0 >= 0.16.0, ac_have_umockdev=yes, ac_have_umockdev=no)
+		PKG_CHECK_MODULES(UMOCKDEV_HOTPLUG, umockdev-1.0 >= 0.17.7, ac_umockdev_hotplug=yes, ac_umockdev_hotplug=no)
+		if test "x$ac_umockdev_hotplug" = xyes; then
+			AC_DEFINE([UMOCKDEV_HOTPLUG], [1], [UMockdev hotplug code is not racy])
+		fi
 	else
 		AC_CHECK_HEADERS([asm/types.h])
 		AC_CHECK_HEADER([linux/netlink.h], [], [AC_MSG_ERROR([Linux netlink header not found])])
@@ -199,17 +210,37 @@
 AC_CHECK_HEADERS([sys/time.h])
 
 if test "x$platform" = xposix; then
-	dnl the clock_gettime() function needs certain clock IDs defined
-	AC_CHECK_FUNCS([clock_gettime], [have_clock_gettime=yes], [have_clock_gettime=])
+	dnl check availability of clock_gettime()
+	if test "x$backend" = xdarwin; then
+		dnl need to verify that OS X target is 10.12 or later for clock_gettime()
+		AC_MSG_CHECKING([whether OS X target version is 10.12 or later])
+		AC_COMPILE_IFELSE([AC_LANG_PROGRAM([
+				#include <AvailabilityMacros.h>
+				#if MAC_OS_X_VERSION_MIN_REQUIRED < 101200
+				# error "Target OS X version is too old"
+				#endif
+			], [])],
+			[AC_MSG_RESULT([yes])
+			 osx_10_12_or_later=yes],
+			[AC_MSG_RESULT([no])
+			 osx_10_12_or_later=])
+		if test "x$osx_10_12_or_later" = xyes; then
+			AC_CHECK_FUNCS([clock_gettime], [have_clock_gettime=yes], [have_clock_gettime=])
+		else
+			AC_MSG_NOTICE([clock_gettime() is not available on target OS X version])
+		fi
+	else
+		AC_CHECK_FUNCS([clock_gettime], [have_clock_gettime=yes], [AC_MSG_ERROR([clock_gettime() is required on this platform])])
+	fi
+
 	if test "x$have_clock_gettime" = xyes; then
+		dnl the clock_gettime() function needs certain clock IDs defined
 		AC_CHECK_DECL([CLOCK_MONOTONIC], [], [AC_MSG_ERROR([C library headers missing definition for CLOCK_MONOTONIC])], [[#include <time.h>]])
 		dnl use the monotonic clock for condition variable timed waits if possible
 		AC_CHECK_FUNCS([pthread_condattr_setclock], [need_clock_realtime=], [need_clock_realtime=yes])
 		if test "x$need_clock_realtime" = xyes; then
 			AC_CHECK_DECL([CLOCK_REALTIME], [], [AC_MSG_ERROR([C library headers missing definition for CLOCK_REALTIME])], [[#include <time.h>]])
 		fi
-	elif test "x$backend" != xdarwin; then
-		AC_MSG_ERROR([clock_gettime() is required on this platform])
 	fi
 fi
 
@@ -335,6 +366,7 @@
 
 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([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])
@@ -372,6 +404,8 @@
 
 AC_SUBST(LT_LDFLAGS)
 
+AC_SUBST([EXTRA_LDFLAGS])
+
 dnl set name of html output directory for doxygen
 AC_SUBST(DOXYGEN_HTMLDIR, [api-1.0])
 
diff --git a/doc/Makefile.in b/doc/Makefile.in
index 45c3209..6568c4f 100644
--- a/doc/Makefile.in
+++ b/doc/Makefile.in
@@ -1,5 +1,5 @@
 LIBUSB_SRC_DIR = @top_srcdir@/libusb
-EXCLUDED_FILES = hotplug.h libusbi.h version.h version_nano.h
+EXCLUDED_FILES = libusbi.h version.h version_nano.h
 LIBUSB_SRC = $(wildcard $(LIBUSB_SRC_DIR)/*.c) $(wildcard $(LIBUSB_SRC_DIR)/*.h)
 LIBUSB_DOC_SRC = $(filter-out $(addprefix $(LIBUSB_SRC_DIR)/,$(EXCLUDED_FILES)),$(LIBUSB_SRC))
 
diff --git a/doc/doxygen.cfg.in b/doc/doxygen.cfg.in
index 569e465..b6e6219 100644
--- a/doc/doxygen.cfg.in
+++ b/doc/doxygen.cfg.in
@@ -1,4 +1,4 @@
-# Doxyfile 1.8.16
+# Doxyfile 1.9.0
 
 # This file describes the settings to be used by the documentation system
 # doxygen (www.doxygen.org) for a project.
@@ -217,6 +217,14 @@
 
 MULTILINE_CPP_IS_BRIEF = NO
 
+# By default Python docstrings are displayed as preformatted text and doxygen's
+# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the
+# doxygen's special commands can be used and the contents of the docstring
+# documentation blocks is shown as doxygen documentation.
+# The default value is: YES.
+
+PYTHON_DOCSTRING       = YES
+
 # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
 # documentation from any documented member that it re-implements.
 # The default value is: YES.
@@ -253,12 +261,6 @@
 
 ALIASES                =
 
-# This tag can be used to specify a number of word-keyword mappings (TCL only).
-# A mapping has the form "name=value". For example adding "class=itcl::class"
-# will allow you to use the command class in the itcl::class meaning.
-
-TCL_SUBST              =
-
 # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
 # only. Doxygen will then generate output that is more tailored for C. For
 # instance, some of the names that are used will be different. The list of all
@@ -299,19 +301,22 @@
 # parses. With this tag you can assign which parser to use for a given
 # extension. Doxygen has a built-in mapping, but you can override or extend it
 # using this tag. The format is ext=language, where ext is a file extension, and
-# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
-# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice,
+# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
+# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL,
 # Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
 # FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
 # tries to guess whether the code is fixed or free formatted code, this is the
-# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat
-# .inc files as Fortran files (default is PHP), and .f files as C (default is
-# Fortran), use: inc=Fortran f=C.
+# default for Fortran type files). For instance to make doxygen treat .inc files
+# as Fortran files (default is PHP), and .f files as C (default is Fortran),
+# use: inc=Fortran f=C.
 #
 # Note: For files without extension you can use no_extension as a placeholder.
 #
 # Note that for custom extensions you also need to set FILE_PATTERNS otherwise
-# the files are not read by doxygen.
+# the files are not read by doxygen. When specifying no_extension you should add
+# * to the FILE_PATTERNS.
+#
+# Note see also the list of default file extension mappings.
 
 EXTENSION_MAPPING      =
 
@@ -445,6 +450,19 @@
 
 LOOKUP_CACHE_SIZE      = 0
 
+# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use
+# during processing. When set to 0 doxygen will based this on the number of
+# cores available in the system. You can set it explicitly to a value larger
+# than 0 to get more control over the balance between CPU load and processing
+# speed. At this moment only the input processing can be done using multiple
+# threads. Since this is still an experimental feature the default is set to 1,
+# which efficively disables parallel processing. Please report any issues you
+# encounter. Generating dot graphs in parallel is controlled by the
+# DOT_NUM_THREADS setting.
+# Minimum value: 0, maximum value: 32, default value: 1.
+
+NUM_PROC_THREADS       = 1
+
 #---------------------------------------------------------------------------
 # Build related configuration options
 #---------------------------------------------------------------------------
@@ -508,6 +526,13 @@
 
 EXTRACT_ANON_NSPACES   = NO
 
+# If this flag is set to YES, the name of an unnamed parameter in a declaration
+# will be determined by the corresponding definition. By default unnamed
+# parameters remain unnamed in the output.
+# The default value is: YES.
+
+RESOLVE_UNNAMED_PARAMS = YES
+
 # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
 # undocumented members inside documented classes or files. If set to NO these
 # members will be included in the various overviews, but no documentation
@@ -525,8 +550,8 @@
 HIDE_UNDOC_CLASSES     = NO
 
 # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
-# (class|struct|union) declarations. If set to NO, these declarations will be
-# included in the documentation.
+# declarations. If set to NO, these declarations will be included in the
+# documentation.
 # The default value is: NO.
 
 HIDE_FRIEND_COMPOUNDS  = NO
@@ -545,11 +570,18 @@
 
 INTERNAL_DOCS          = NO
 
-# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
-# names in lower-case letters. If set to YES, upper-case letters are also
-# allowed. This is useful if you have classes or files whose names only differ
-# in case and if your file system supports case sensitive file names. Windows
-# (including Cygwin) ands Mac users are advised to set this option to NO.
+# With the correct setting of option CASE_SENSE_NAMES doxygen will better be
+# able to match the capabilities of the underlying filesystem. In case the
+# filesystem is case sensitive (i.e. it supports files in the same directory
+# whose names only differ in casing), the option must be set to YES to properly
+# deal with such files in case they appear in the input. For filesystems that
+# are not case sensitive the option should be be set to NO to properly deal with
+# output files written for symbols that only differ in casing, such as for two
+# classes, one named CLASS and the other named Class, and to also support
+# references to files without having to specify the exact matching casing. On
+# Windows (including Cygwin) and MacOS, users should typically set this option
+# to NO, whereas on Linux or other Unix flavors it should typically be set to
+# YES.
 # The default value is: system dependent.
 
 CASE_SENSE_NAMES       = YES
@@ -788,7 +820,10 @@
 WARN_NO_PARAMDOC       = NO
 
 # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
-# a warning is encountered.
+# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS
+# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but
+# at the end of the doxygen process doxygen will return with a non-zero status.
+# Possible values are: NO, YES and FAIL_ON_WARNINGS.
 # The default value is: NO.
 
 WARN_AS_ERROR          = NO
@@ -824,8 +859,8 @@
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
 # libiconv (or the iconv built into libc) for the transcoding. See the libiconv
-# documentation (see: https://www.gnu.org/software/libiconv/) for the list of
-# possible encodings.
+# documentation (see:
+# https://www.gnu.org/software/libiconv/) for the list of possible encodings.
 # The default value is: UTF-8.
 
 INPUT_ENCODING         = UTF-8
@@ -838,11 +873,15 @@
 # need to set EXTENSION_MAPPING for the extension otherwise the files are not
 # read by doxygen.
 #
+# Note the list of default checked file patterns might differ from the list of
+# default file extension mappings.
+#
 # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
 # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
 # *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
-# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
-# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice.
+# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),
+# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl,
+# *.ucf, *.qsf and *.ice.
 
 FILE_PATTERNS          = *.c \
                          *.h
@@ -860,8 +899,7 @@
 # Note that relative paths are relative to the directory from which doxygen is
 # run.
 
-EXCLUDE                = @top_srcdir@/libusb/hotplug.h \
-                         @top_srcdir@/libusb/libusbi.h \
+EXCLUDE                = @top_srcdir@/libusb/libusbi.h \
                          @top_srcdir@/libusb/version.h \
                          @top_srcdir@/libusb/version_nano.h \
                          @top_srcdir@/libusb/os
@@ -1072,13 +1110,6 @@
 
 ALPHABETICAL_INDEX     = YES
 
-# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
-# which the alphabetical index list will be split.
-# Minimum value: 1, maximum value: 20, default value: 5.
-# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
-
-COLS_IN_ALPHA_INDEX    = 5
-
 # In case all classes in a project start with a common prefix, all classes will
 # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
 # can be used to specify a prefix (or a list of prefixes) that should be ignored
@@ -1217,9 +1248,9 @@
 
 # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
 # documentation will contain a main index with vertical navigation menus that
-# are dynamically created via Javascript. If disabled, the navigation index will
+# are dynamically created via JavaScript. If disabled, the navigation index will
 # consists of multiple levels of tabs that are statically embedded in every HTML
-# page. Disable this option to support browsers that do not have Javascript,
+# page. Disable this option to support browsers that do not have JavaScript,
 # like the Qt help browser.
 # The default value is: YES.
 # This tag requires that the tag GENERATE_HTML is set to YES.
@@ -1249,10 +1280,11 @@
 
 # If the GENERATE_DOCSET tag is set to YES, additional index files will be
 # generated that can be used as input for Apple's Xcode 3 integrated development
-# environment (see: https://developer.apple.com/xcode/), introduced with OSX
-# 10.5 (Leopard). To create a documentation set, doxygen will generate a
-# Makefile in the HTML output directory. Running make will produce the docset in
-# that directory and running make install will install the docset in
+# environment (see:
+# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To
+# create a documentation set, doxygen will generate a Makefile in the HTML
+# output directory. Running make will produce the docset in that directory and
+# running make install will install the docset in
 # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
 # startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
 # genXcode/_index.html for more information.
@@ -1294,8 +1326,8 @@
 # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
 # additional HTML index files: index.hhp, index.hhc, and index.hhk. The
 # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
-# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on
-# Windows.
+# (see:
+# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows.
 #
 # The HTML Help Workshop contains a compiler that can convert all HTML output
 # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
@@ -1325,7 +1357,7 @@
 HHC_LOCATION           =
 
 # The GENERATE_CHI flag controls if a separate .chi index file is generated
-# (YES) or that it should be included in the master .chm file (NO).
+# (YES) or that it should be included in the main .chm file (NO).
 # The default value is: NO.
 # This tag requires that the tag GENERATE_HTMLHELP is set to YES.
 
@@ -1370,7 +1402,8 @@
 
 # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
 # Project output. For more information please see Qt Help Project / Namespace
-# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
+# (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
 # The default value is: org.doxygen.Project.
 # This tag requires that the tag GENERATE_QHP is set to YES.
 
@@ -1378,8 +1411,8 @@
 
 # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
 # Help Project output. For more information please see Qt Help Project / Virtual
-# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-
-# folders).
+# Folders (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders).
 # The default value is: doc.
 # This tag requires that the tag GENERATE_QHP is set to YES.
 
@@ -1387,16 +1420,16 @@
 
 # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
 # filter to add. For more information please see Qt Help Project / Custom
-# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
-# filters).
+# Filters (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
 # This tag requires that the tag GENERATE_QHP is set to YES.
 
 QHP_CUST_FILTER_NAME   =
 
 # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
 # custom filter to add. For more information please see Qt Help Project / Custom
-# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-
-# filters).
+# Filters (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
 # This tag requires that the tag GENERATE_QHP is set to YES.
 
 QHP_CUST_FILTER_ATTRS  =
@@ -1408,9 +1441,9 @@
 
 QHP_SECT_FILTER_ATTRS  =
 
-# The QHG_LOCATION tag can be used to specify the location of Qt's
-# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
-# generated .qhp file.
+# The QHG_LOCATION tag can be used to specify the location (absolute path
+# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to
+# run qhelpgenerator on the generated .qhp file.
 # This tag requires that the tag GENERATE_QHP is set to YES.
 
 QHG_LOCATION           =
@@ -1487,6 +1520,17 @@
 
 EXT_LINKS_IN_WINDOW    = NO
 
+# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
+# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
+# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
+# the HTML output. These images will generally look nicer at scaled resolutions.
+# Possible values are: png (the default) and svg (looks nicer but requires the
+# pdf2svg or inkscape tool).
+# The default value is: png.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FORMULA_FORMAT    = png
+
 # Use this tag to change the font size of LaTeX formulas included as images in
 # the HTML documentation. When you change the font size after a successful
 # doxygen run you need to manually remove any form_*.png images from the HTML
@@ -1507,8 +1551,14 @@
 
 FORMULA_TRANSPARENT    = YES
 
+# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
+# to create new LaTeX commands to be used in formulas as building blocks. See
+# the section "Including formulas" for details.
+
+FORMULA_MACROFILE      =
+
 # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
-# https://www.mathjax.org) which uses client side Javascript for the rendering
+# https://www.mathjax.org) which uses client side JavaScript for the rendering
 # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
 # installed or if you want to formulas look prettier in the HTML output. When
 # enabled you may also need to install MathJax separately and configure the path
@@ -1520,7 +1570,7 @@
 
 # When MathJax is enabled you can set the default output format to be used for
 # the MathJax output. See the MathJax site (see:
-# http://docs.mathjax.org/en/latest/output.html) for more details.
+# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details.
 # Possible values are: HTML-CSS (which is slower, but has the best
 # compatibility), NativeMML (i.e. MathML) and SVG.
 # The default value is: HTML-CSS.
@@ -1536,7 +1586,7 @@
 # Content Delivery Network so you can quickly see the result without installing
 # MathJax. However, it is strongly recommended to install a local copy of
 # MathJax from https://www.mathjax.org before deployment.
-# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/.
+# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2.
 # This tag requires that the tag USE_MATHJAX is set to YES.
 
 MATHJAX_RELPATH        = http://cdn.mathjax.org/mathjax/latest
@@ -1550,7 +1600,8 @@
 
 # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
 # of code that will be used on startup of the MathJax code. See the MathJax site
-# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
+# (see:
+# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an
 # example see the documentation.
 # This tag requires that the tag USE_MATHJAX is set to YES.
 
@@ -1578,7 +1629,7 @@
 SEARCHENGINE           = NO
 
 # When the SERVER_BASED_SEARCH tag is enabled the search engine will be
-# implemented using a web server instead of a web client using Javascript. There
+# implemented using a web server instead of a web client using JavaScript. There
 # are two flavors of web server based searching depending on the EXTERNAL_SEARCH
 # setting. When disabled, doxygen will generate a PHP script for searching and
 # an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
@@ -1597,7 +1648,8 @@
 #
 # Doxygen ships with an example indexer (doxyindexer) and search engine
 # (doxysearch.cgi) which are based on the open source search engine library
-# Xapian (see: https://xapian.org/).
+# Xapian (see:
+# https://xapian.org/).
 #
 # See the section "External Indexing and Searching" for details.
 # The default value is: NO.
@@ -1610,8 +1662,9 @@
 #
 # Doxygen ships with an example indexer (doxyindexer) and search engine
 # (doxysearch.cgi) which are based on the open source search engine library
-# Xapian (see: https://xapian.org/). See the section "External Indexing and
-# Searching" for details.
+# Xapian (see:
+# https://xapian.org/). See the section "External Indexing and Searching" for
+# details.
 # This tag requires that the tag SEARCHENGINE is set to YES.
 
 SEARCHENGINE_URL       =
@@ -1706,7 +1759,7 @@
 # The default value is: a4.
 # This tag requires that the tag GENERATE_LATEX is set to YES.
 
-PAPER_TYPE             = a4wide
+PAPER_TYPE             = a4
 
 # The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
 # that should be included in the LaTeX output. The package can be specified just
@@ -1775,9 +1828,11 @@
 
 PDF_HYPERLINKS         = NO
 
-# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
-# the PDF file directly from the LaTeX files. Set this option to YES, to get a
-# higher quality PDF documentation.
+# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as
+# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX
+# files. Set this option to YES, to get a higher quality PDF documentation.
+#
+# See also section LATEX_CMD_NAME for selecting the engine.
 # The default value is: YES.
 # This tag requires that the tag GENERATE_LATEX is set to YES.
 
@@ -2291,10 +2346,32 @@
 # but if the number exceeds 15, the total amount of fields shown is limited to
 # 10.
 # Minimum value: 0, maximum value: 100, default value: 10.
-# This tag requires that the tag HAVE_DOT is set to YES.
+# This tag requires that the tag UML_LOOK is set to YES.
 
 UML_LIMIT_NUM_FIELDS   = 10
 
+# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and
+# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS
+# tag is set to YES, doxygen will add type and arguments for attributes and
+# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen
+# will not generate fields with class member information in the UML graphs. The
+# class diagrams will look similar to the default class diagrams but using UML
+# notation for the relationships.
+# Possible values are: NO, YES and NONE.
+# The default value is: NO.
+# This tag requires that the tag UML_LOOK is set to YES.
+
+DOT_UML_DETAILS        = NO
+
+# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters
+# to display on a single line. If the actual line length exceeds this threshold
+# significantly it will wrapped across multiple lines. Some heuristics are apply
+# to avoid ugly line breaks.
+# Minimum value: 0, maximum value: 1000, default value: 17.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_WRAP_THRESHOLD     = 17
+
 # If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
 # collaboration graphs will show the relations between templates and their
 # instances.
@@ -2484,9 +2561,11 @@
 
 GENERATE_LEGEND        = YES
 
-# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate
 # files that are used to generate the various graphs.
+#
+# Note: This setting is not only used for dot files but also for msc and
+# plantuml temporary files.
 # The default value is: YES.
-# This tag requires that the tag HAVE_DOT is set to YES.
 
 DOT_CLEANUP            = YES
diff --git a/examples/dpfp.c b/examples/dpfp.c
index a3a76df..6828650 100644
--- a/examples/dpfp.c
+++ b/examples/dpfp.c
@@ -217,7 +217,11 @@
 static int find_dpfp_device(void)
 {
 	devh = libusb_open_device_with_vid_pid(NULL, 0x05ba, 0x000a);
-	return devh ? 0 : -ENODEV;
+	if (!devh) {
+		errno = ENODEV;
+		return -1;
+	}
+	return 0;
 }
 
 static int print_f0_data(void)
@@ -316,13 +320,16 @@
 	unsigned char *buf = malloc(LIBUSB_CONTROL_SETUP_SIZE + 1);
 	struct libusb_transfer *transfer;
 
-	if (!buf)
-		return -ENOMEM;
+	if (!buf) {
+		errno = ENOMEM;
+		return -1;
+	}
 
 	transfer = libusb_alloc_transfer(0);
 	if (!transfer) {
 		free(buf);
-		return -ENOMEM;
+		errno = ENOMEM;
+		return -1;
 	}
 
 	printf("async set mode %02x\n", data);
@@ -547,12 +554,16 @@
 static int alloc_transfers(void)
 {
 	img_transfer = libusb_alloc_transfer(0);
-	if (!img_transfer)
-		return -ENOMEM;
+	if (!img_transfer) {
+		errno = ENOMEM;
+		return -1;
+	}
 
 	irq_transfer = libusb_alloc_transfer(0);
-	if (!irq_transfer)
-		return -ENOMEM;
+	if (!irq_transfer) {
+		errno = ENOMEM;
+		return -1;
+	}
 
 	libusb_fill_bulk_transfer(img_transfer, devh, EP_DATA, imgbuf,
 		sizeof(imgbuf), cb_img, NULL, 0);
diff --git a/examples/ezusb.c b/examples/ezusb.c
index 6abd47d..4bed12a 100644
--- a/examples/ezusb.c
+++ b/examples/ezusb.c
@@ -139,7 +139,11 @@
 		else
 			logerror("%s ==> %d\n", label, status);
 	}
-	return (status < 0) ? -EIO : 0;
+	if (status < 0) {
+		errno = EIO;
+		return -1;
+	}
+	return 0;
 }
 
 /*
@@ -162,7 +166,11 @@
 		else
 			logerror("%s ==> %d\n", label, status);
 	}
-	return (status < 0) ? -EIO : 0;
+	if (status < 0) {
+		errno = EIO;
+		return -1;
+	}
+	return 0;
 }
 
 /*
@@ -195,7 +203,7 @@
 }
 
 /*
- * Send an FX3 jumpt to address command
+ * Send an FX3 jump to address command
  * Returns false on error.
  */
 static bool ezusb_fx3_jump(libusb_device_handle *device, uint32_t addr)
@@ -514,7 +522,8 @@
 		if (external) {
 			logerror("can't write %u bytes external memory at 0x%08x\n",
 				(unsigned)len, addr);
-			return -EINVAL;
+			errno = EINVAL;
+			return -1;
 		}
 		break;
 	case skip_internal:		/* CPU must be running */
@@ -538,7 +547,8 @@
 	case _undef:
 	default:
 		logerror("bug\n");
-		return -EDOM;
+		errno = EDOM;
+		return -1;
 	}
 
 	ctx->total += len;
diff --git a/examples/sam3u_benchmark.c b/examples/sam3u_benchmark.c
index 33e8913..8979775 100644
--- a/examples/sam3u_benchmark.c
+++ b/examples/sam3u_benchmark.c
@@ -122,8 +122,10 @@
 		num_iso_pack = 16;
 
 	xfr = libusb_alloc_transfer(num_iso_pack);
-	if (!xfr)
-		return -ENOMEM;
+	if (!xfr) {
+		errno = ENOMEM;
+		return -1;
+	}
 
 	if (ep == EP_ISO_IN) {
 		libusb_fill_iso_transfer(xfr, devh, ep, buf,
diff --git a/examples/xusb.c b/examples/xusb.c
index bf328fe..5c65298 100644
--- a/examples/xusb.c
+++ b/examples/xusb.c
@@ -220,7 +220,7 @@
 			printf("\tRIGHT 3 pressed\n");
 			break;
 		case 0x08:
-			printf("\tSTART presed\n");
+			printf("\tSTART pressed\n");
 			break;
 		case 0x10:
 			printf("\tUP pressed\n");
@@ -246,7 +246,7 @@
 			printf("\tLEFT 1 pressed\n");
 			break;
 		case 0x08:
-			printf("\tRIGHT 1 presed\n");
+			printf("\tRIGHT 1 pressed\n");
 			break;
 		case 0x10:
 			printf("\tTRIANGLE pressed\n");
@@ -863,6 +863,8 @@
 
 	printf("\nReading first configuration descriptor:\n");
 	CALL_CHECK_CLOSE(libusb_get_config_descriptor(dev, 0, &conf_desc), handle);
+	printf("              total length: %d\n", conf_desc->wTotalLength);
+	printf("         descriptor length: %d\n", conf_desc->bLength);
 	nb_ifaces = conf_desc->bNumInterfaces;
 	printf("             nb interfaces: %d\n", nb_ifaces);
 	if (nb_ifaces > 0)
@@ -914,6 +916,8 @@
 	libusb_set_auto_detach_kernel_driver(handle, 1);
 	for (iface = 0; iface < nb_ifaces; iface++)
 	{
+		int ret = libusb_kernel_driver_active(handle, iface);
+		printf("\nKernel driver attached for interface %d: %d\n", iface, ret);
 		printf("\nClaiming interface %d...\n", iface);
 		r = libusb_claim_interface(handle, iface);
 		if (r != LIBUSB_SUCCESS) {
@@ -930,12 +934,16 @@
 			printf("   String (0x%02X): \"%s\"\n", string_index[i], string);
 		}
 	}
-	// Read the OS String Descriptor
+
+	printf("\nReading OS string descriptor:");
 	r = libusb_get_string_descriptor(handle, MS_OS_DESC_STRING_INDEX, 0, (unsigned char*)string, MS_OS_DESC_STRING_LENGTH);
 	if (r == MS_OS_DESC_STRING_LENGTH && memcmp(ms_os_desc_string, string, sizeof(ms_os_desc_string)) == 0) {
 		// If this is a Microsoft OS String Descriptor,
 		// attempt to read the WinUSB extended Feature Descriptors
+		printf("\n");
 		read_ms_winsub_feature_descriptors(handle, string[MS_OS_DESC_VENDOR_CODE_OFFSET], first_iface);
+	} else {
+		printf(" no descriptor\n");
 	}
 
 	switch(test_mode) {
diff --git a/include/libusb/hotplug.h b/include/libusb/hotplug.h
deleted file mode 120000
index 1f6670a..0000000
--- a/include/libusb/hotplug.h
+++ /dev/null
@@ -1 +0,0 @@
-../../libusb/hotplug.h
\ No newline at end of file
diff --git a/libusb/Makefile.am b/libusb/Makefile.am
index c78006e..3475c9a 100644
--- a/libusb/Makefile.am
+++ b/libusb/Makefile.am
@@ -80,9 +80,9 @@
 endif
 endif
 
-libusb_1_0_la_LDFLAGS = $(LT_LDFLAGS)
+libusb_1_0_la_LDFLAGS = $(LT_LDFLAGS) $(EXTRA_LDFLAGS)
 libusb_1_0_la_SOURCES = libusbi.h version.h version_nano.h \
-	core.c descriptor.c hotplug.h hotplug.c io.c strerror.c sync.c \
+	core.c descriptor.c hotplug.c io.c strerror.c sync.c \
 	$(PLATFORM_SRC) $(OS_SRC)
 
 pkginclude_HEADERS = libusb.h
diff --git a/libusb/core.c b/libusb/core.c
index 07d459c..ec429b7 100644
--- a/libusb/core.c
+++ b/libusb/core.c
@@ -21,7 +21,6 @@
  */
 
 #include "libusbi.h"
-#include "hotplug.h"
 #include "version.h"
 
 #ifdef __ANDROID__
@@ -33,17 +32,21 @@
 #include <syslog.h>
 #endif
 
-struct libusb_context *usbi_default_context;
 static const struct libusb_version libusb_version_internal =
 	{ LIBUSB_MAJOR, LIBUSB_MINOR, LIBUSB_MICRO, LIBUSB_NANO,
 	  LIBUSB_RC, "http://libusb.info" };
-static int default_context_refcnt;
-static usbi_mutex_static_t default_context_lock = USBI_MUTEX_INITIALIZER;
 static struct timespec timestamp_origin;
 #if defined(ENABLE_LOGGING) && !defined(USE_SYSTEM_LOGGING_FACILITY)
 static libusb_log_cb log_handler;
 #endif
 
+struct libusb_context *usbi_default_context;
+struct libusb_context *usbi_fallback_context;
+static int default_context_refcnt;
+static usbi_mutex_static_t default_context_lock = USBI_MUTEX_INITIALIZER;
+static struct usbi_option default_context_options[LIBUSB_OPTION_MAX];
+
+
 usbi_mutex_static_t active_contexts_lock = USBI_MUTEX_INITIALIZER;
 struct list_head active_contexts_list;
 
@@ -310,7 +313,8 @@
  * - libusb is able to send a packet of zero length to an endpoint simply by
  * submitting a transfer of zero length.
  * - The \ref libusb_transfer_flags::LIBUSB_TRANSFER_ADD_ZERO_PACKET
- * "LIBUSB_TRANSFER_ADD_ZERO_PACKET" flag is currently only supported on Linux.
+ * "LIBUSB_TRANSFER_ADD_ZERO_PACKET" flag is currently supported on Linux,
+ * Darwin and Windows (WinUSB).
  */
 
 /**
@@ -577,7 +581,9 @@
  *
  * The libusb_get_device_list() function can be used to obtain a list of
  * devices currently connected to the system. This is known as device
- * discovery.
+ * discovery. Devices can also be discovered with the hotplug mechanism,
+ * whereby a callback function registered with libusb_hotplug_register_callback()
+ * will be called when a device of interest is connected or disconnected.
  *
  * Just because you have a reference to a device does not mean it is
  * necessarily usable. The device may have been unplugged, you may not have
@@ -608,7 +614,7 @@
  *
  * With the above information in mind, the process of opening a device can
  * be viewed as follows:
- * -# Discover devices using libusb_get_device_list().
+ * -# Discover devices using libusb_get_device_list() or libusb_hotplug_register_callback().
  * -# Choose the device that you want to operate, and call libusb_open().
  * -# Unref all devices in the discovered device list.
  * -# Free the discovered device list.
@@ -633,7 +639,7 @@
  * which grows when required. it can be freed once discovery has completed,
  * eliminating the need for a list node in the libusb_device structure
  * itself. */
-#define DISCOVERED_DEVICES_SIZE_STEP 8
+#define DISCOVERED_DEVICES_SIZE_STEP 16
 
 static struct discovered_devs *discovered_devs_alloc(void)
 {
@@ -674,7 +680,7 @@
 	}
 
 	/* exceeded capacity, need to grow */
-	usbi_dbg("need to increase capacity");
+	usbi_dbg(DEVICE_CTX(dev), "need to increase capacity");
 	capacity = discdevs->capacity + DISCOVERED_DEVICES_SIZE_STEP;
 	/* can't use usbi_reallocf here because in failure cases it would
 	 * free the existing discdevs without unreferencing its devices. */
@@ -704,16 +710,14 @@
 	if (!dev)
 		return NULL;
 
-	usbi_mutex_init(&dev->lock);
+	usbi_atomic_store(&dev->refcnt, 1);
 
 	dev->ctx = ctx;
-	dev->refcnt = 1;
 	dev->session_data = session_id;
 	dev->speed = LIBUSB_SPEED_UNKNOWN;
 
-	if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) {
-		usbi_connect_device (dev);
-	}
+	if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG))
+		usbi_connect_device(dev);
 
 	return dev;
 }
@@ -722,39 +726,26 @@
 {
 	struct libusb_context *ctx = DEVICE_CTX(dev);
 
-	dev->attached = 1;
+	usbi_atomic_store(&dev->attached, 1);
 
 	usbi_mutex_lock(&dev->ctx->usb_devs_lock);
 	list_add(&dev->list, &dev->ctx->usb_devs);
 	usbi_mutex_unlock(&dev->ctx->usb_devs_lock);
 
-	/* Signal that an event has occurred for this device if we support hotplug AND
-	 * the hotplug message list is ready. This prevents an event from getting raised
-	 * during initial enumeration. */
-	if (libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG) && dev->ctx->hotplug_msgs.next) {
-		usbi_hotplug_notification(ctx, dev, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED);
-	}
+	usbi_hotplug_notification(ctx, dev, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED);
 }
 
 void usbi_disconnect_device(struct libusb_device *dev)
 {
 	struct libusb_context *ctx = DEVICE_CTX(dev);
 
-	usbi_mutex_lock(&dev->lock);
-	dev->attached = 0;
-	usbi_mutex_unlock(&dev->lock);
+	usbi_atomic_store(&dev->attached, 0);
 
 	usbi_mutex_lock(&ctx->usb_devs_lock);
 	list_del(&dev->list);
 	usbi_mutex_unlock(&ctx->usb_devs_lock);
 
-	/* Signal that an event has occurred for this device if we support hotplug AND
-	 * the hotplug message list is ready. This prevents an event from getting raised
-	 * during initial enumeration. libusb_handle_events will take care of dereferencing
-	 * the device. */
-	if (libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG) && dev->ctx->hotplug_msgs.next) {
-		usbi_hotplug_notification(ctx, dev, LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT);
-	}
+	usbi_hotplug_notification(ctx, dev, LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT);
 }
 
 /* Perform some final sanity checks on a newly discovered device. If this
@@ -775,7 +766,7 @@
 		usbi_err(DEVICE_CTX(dev), "too many configurations");
 		return LIBUSB_ERROR_IO;
 	} else if (0 == num_configurations) {
-		usbi_dbg("zero configurations, maybe an unauthorized device");
+		usbi_dbg(DEVICE_CTX(dev), "zero configurations, maybe an unauthorized device");
 	}
 
 	return 0;
@@ -830,7 +821,7 @@
 	int r = 0;
 	ssize_t i, len;
 
-	usbi_dbg(" ");
+	usbi_dbg(ctx, " ");
 
 	if (!discdevs)
 		return LIBUSB_ERROR_NO_MEM;
@@ -1173,9 +1164,11 @@
 DEFAULT_VISIBILITY
 libusb_device * LIBUSB_CALL libusb_ref_device(libusb_device *dev)
 {
-	usbi_mutex_lock(&dev->lock);
-	dev->refcnt++;
-	usbi_mutex_unlock(&dev->lock);
+	long refcnt;
+
+	refcnt = usbi_atomic_inc(&dev->refcnt);
+	assert(refcnt >= 2);
+
 	return dev;
 }
 
@@ -1186,17 +1179,16 @@
  */
 void API_EXPORTED libusb_unref_device(libusb_device *dev)
 {
-	int refcnt;
+	long refcnt;
 
 	if (!dev)
 		return;
 
-	usbi_mutex_lock(&dev->lock);
-	refcnt = --dev->refcnt;
-	usbi_mutex_unlock(&dev->lock);
+	refcnt = usbi_atomic_dec(&dev->refcnt);
+	assert(refcnt >= 0);
 
 	if (refcnt == 0) {
-		usbi_dbg("destroy device %d.%d", dev->bus_number, dev->device_address);
+		usbi_dbg(DEVICE_CTX(dev), "destroy device %d.%d", dev->bus_number, dev->device_address);
 
 		libusb_unref_device(dev->parent_dev);
 
@@ -1208,7 +1200,6 @@
 			usbi_disconnect_device(dev);
 		}
 
-		usbi_mutex_destroy(&dev->lock);
 		free(dev);
 	}
 }
@@ -1218,8 +1209,10 @@
  * handle for the underlying device. The handle allows you to use libusb to
  * perform I/O on the device in question.
  *
- * Must call libusb_set_option(NULL, LIBUSB_OPTION_WEAK_AUTHORITY)
- * before libusb_init if don't have authority to access the usb device directly.
+ * Call libusb_set_option(NULL, LIBUSB_OPTION_NO_DEVICE_DISCOVERY) before
+ * libusb_init() if you want to skip enumeration of USB devices. In particular,
+ * this might be needed on Android if you don't have authority to access USB
+ * devices in general.
  *
  * On Linux, the system device handle must be a valid file descriptor opened
  * on the device node.
@@ -1233,6 +1226,8 @@
  *
  * This is a non-blocking function; no requests are sent over the bus.
  *
+ * Since version 1.0.23, \ref LIBUSB_API_VERSION >= 0x01000107
+ *
  * \param ctx the context to operate on, or NULL for the default context
  * \param sys_dev the platform-specific system device handle
  * \param dev_handle output location for the returned device handle pointer. Only
@@ -1251,7 +1246,7 @@
 	size_t priv_size = usbi_backend.device_handle_priv_size;
 	int r;
 
-	usbi_dbg("wrap_sys_device 0x%" PRIxPTR, (uintptr_t)sys_dev);
+	usbi_dbg(ctx, "wrap_sys_device 0x%" PRIxPTR, (uintptr_t)sys_dev);
 
 	ctx = usbi_get_context(ctx);
 
@@ -1266,7 +1261,7 @@
 
 	r = usbi_backend.wrap_sys_device(ctx, _dev_handle, sys_dev);
 	if (r < 0) {
-		usbi_dbg("wrap_sys_device 0x%" PRIxPTR " returns %d", (uintptr_t)sys_dev, r);
+		usbi_dbg(ctx, "wrap_sys_device 0x%" PRIxPTR " returns %d", (uintptr_t)sys_dev, r);
 		usbi_mutex_destroy(&_dev_handle->lock);
 		free(_dev_handle);
 		return r;
@@ -1306,11 +1301,11 @@
 	struct libusb_device_handle *_dev_handle;
 	size_t priv_size = usbi_backend.device_handle_priv_size;
 	int r;
-	usbi_dbg("open %d.%d", dev->bus_number, dev->device_address);
 
-	if (!dev->attached) {
+	usbi_dbg(DEVICE_CTX(dev), "open %d.%d", dev->bus_number, dev->device_address);
+
+	if (!usbi_atomic_load(&dev->attached))
 		return LIBUSB_ERROR_NO_DEVICE;
-	}
 
 	_dev_handle = calloc(1, PTR_ALIGN(sizeof(*_dev_handle)) + priv_size);
 	if (!_dev_handle)
@@ -1322,7 +1317,7 @@
 
 	r = usbi_backend.open(_dev_handle);
 	if (r < 0) {
-		usbi_dbg("open %d.%d returns %d", dev->bus_number, dev->device_address, r);
+		usbi_dbg(DEVICE_CTX(dev), "open %d.%d returns %d", dev->bus_number, dev->device_address, r);
 		libusb_unref_device(dev);
 		usbi_mutex_destroy(&_dev_handle->lock);
 		free(_dev_handle);
@@ -1428,7 +1423,7 @@
 		 * just making sure that we don't attempt to process the transfer after
 		 * the device handle is invalid
 		 */
-		usbi_dbg("Removed transfer %p from the in-flight list because device handle %p closed",
+		usbi_dbg(ctx, "Removed transfer %p from the in-flight list because device handle %p closed",
 			 transfer, dev_handle);
 	}
 	usbi_mutex_unlock(&ctx->flying_transfers_lock);
@@ -1462,9 +1457,9 @@
 
 	if (!dev_handle)
 		return;
-	usbi_dbg(" ");
-
 	ctx = HANDLE_CTX(dev_handle);
+	usbi_dbg(ctx, " ");
+
 	handling_events = usbi_handling_events(ctx);
 
 	/* Similarly to libusb_open(), we want to interrupt all event handlers
@@ -1546,27 +1541,28 @@
 {
 	int r = LIBUSB_ERROR_NOT_SUPPORTED;
 	uint8_t tmp = 0;
+	struct libusb_context *ctx = HANDLE_CTX(dev_handle);
 
-	usbi_dbg(" ");
+	usbi_dbg(ctx, " ");
 	if (usbi_backend.get_configuration)
 		r = usbi_backend.get_configuration(dev_handle, &tmp);
 
 	if (r == LIBUSB_ERROR_NOT_SUPPORTED) {
-		usbi_dbg("falling back to control message");
+		usbi_dbg(ctx, "falling back to control message");
 		r = libusb_control_transfer(dev_handle, LIBUSB_ENDPOINT_IN,
 			LIBUSB_REQUEST_GET_CONFIGURATION, 0, 0, &tmp, 1, 1000);
 		if (r == 1) {
 			r = 0;
 		} else if (r == 0) {
-			usbi_err(HANDLE_CTX(dev_handle), "zero bytes returned in ctrl transfer?");
+			usbi_err(ctx, "zero bytes returned in ctrl transfer?");
 			r = LIBUSB_ERROR_IO;
 		} else {
-			usbi_dbg("control failed, error %d", r);
+			usbi_dbg(ctx, "control failed, error %d", r);
 		}
 	}
 
 	if (r == 0) {
-		usbi_dbg("active config %u", tmp);
+		usbi_dbg(ctx, "active config %u", tmp);
 		*config = (int)tmp;
 	}
 
@@ -1630,7 +1626,7 @@
 int API_EXPORTED libusb_set_configuration(libusb_device_handle *dev_handle,
 	int configuration)
 {
-	usbi_dbg("configuration %d", configuration);
+	usbi_dbg(HANDLE_CTX(dev_handle), "configuration %d", configuration);
 	if (configuration < -1 || configuration > (int)UINT8_MAX)
 		return LIBUSB_ERROR_INVALID_PARAM;
 	return usbi_backend.set_configuration(dev_handle, configuration);
@@ -1669,11 +1665,11 @@
 {
 	int r = 0;
 
-	usbi_dbg("interface %d", interface_number);
+	usbi_dbg(HANDLE_CTX(dev_handle), "interface %d", interface_number);
 	if (interface_number < 0 || interface_number >= USB_MAXINTERFACES)
 		return LIBUSB_ERROR_INVALID_PARAM;
 
-	if (!dev_handle->dev->attached)
+	if (!usbi_atomic_load(&dev_handle->dev->attached))
 		return LIBUSB_ERROR_NO_DEVICE;
 
 	usbi_mutex_lock(&dev_handle->lock);
@@ -1713,7 +1709,7 @@
 {
 	int r;
 
-	usbi_dbg("interface %d", interface_number);
+	usbi_dbg(HANDLE_CTX(dev_handle), "interface %d", interface_number);
 	if (interface_number < 0 || interface_number >= USB_MAXINTERFACES)
 		return LIBUSB_ERROR_INVALID_PARAM;
 
@@ -1756,19 +1752,19 @@
 int API_EXPORTED libusb_set_interface_alt_setting(libusb_device_handle *dev_handle,
 	int interface_number, int alternate_setting)
 {
-	usbi_dbg("interface %d altsetting %d",
+	usbi_dbg(HANDLE_CTX(dev_handle), "interface %d altsetting %d",
 		interface_number, alternate_setting);
 	if (interface_number < 0 || interface_number >= USB_MAXINTERFACES)
 		return LIBUSB_ERROR_INVALID_PARAM;
 	if (alternate_setting < 0 || alternate_setting > (int)UINT8_MAX)
 		return LIBUSB_ERROR_INVALID_PARAM;
 
-	usbi_mutex_lock(&dev_handle->lock);
-	if (!dev_handle->dev->attached) {
+	if (!usbi_atomic_load(&dev_handle->dev->attached)) {
 		usbi_mutex_unlock(&dev_handle->lock);
 		return LIBUSB_ERROR_NO_DEVICE;
 	}
 
+	usbi_mutex_lock(&dev_handle->lock);
 	if (!(dev_handle->claimed_interfaces & (1U << interface_number))) {
 		usbi_mutex_unlock(&dev_handle->lock);
 		return LIBUSB_ERROR_NOT_FOUND;
@@ -1798,8 +1794,8 @@
 int API_EXPORTED libusb_clear_halt(libusb_device_handle *dev_handle,
 	unsigned char endpoint)
 {
-	usbi_dbg("endpoint %x", endpoint);
-	if (!dev_handle->dev->attached)
+	usbi_dbg(HANDLE_CTX(dev_handle), "endpoint 0x%x", endpoint);
+	if (!usbi_atomic_load(&dev_handle->dev->attached))
 		return LIBUSB_ERROR_NO_DEVICE;
 
 	return usbi_backend.clear_halt(dev_handle, endpoint);
@@ -1826,8 +1822,8 @@
  */
 int API_EXPORTED libusb_reset_device(libusb_device_handle *dev_handle)
 {
-	usbi_dbg(" ");
-	if (!dev_handle->dev->attached)
+	usbi_dbg(HANDLE_CTX(dev_handle), " ");
+	if (!usbi_atomic_load(&dev_handle->dev->attached))
 		return LIBUSB_ERROR_NO_DEVICE;
 
 	if (usbi_backend.reset_device)
@@ -1860,12 +1856,12 @@
 int API_EXPORTED libusb_alloc_streams(libusb_device_handle *dev_handle,
 	uint32_t num_streams, unsigned char *endpoints, int num_endpoints)
 {
-	usbi_dbg("streams %u eps %d", (unsigned)num_streams, num_endpoints);
+	usbi_dbg(HANDLE_CTX(dev_handle), "streams %u eps %d", (unsigned)num_streams, num_endpoints);
 
 	if (!num_streams || !endpoints || num_endpoints <= 0)
 		return LIBUSB_ERROR_INVALID_PARAM;
 
-	if (!dev_handle->dev->attached)
+	if (!usbi_atomic_load(&dev_handle->dev->attached))
 		return LIBUSB_ERROR_NO_DEVICE;
 
 	if (usbi_backend.alloc_streams)
@@ -1890,12 +1886,12 @@
 int API_EXPORTED libusb_free_streams(libusb_device_handle *dev_handle,
 	unsigned char *endpoints, int num_endpoints)
 {
-	usbi_dbg("eps %d", num_endpoints);
+	usbi_dbg(HANDLE_CTX(dev_handle), "eps %d", num_endpoints);
 
 	if (!endpoints || num_endpoints <= 0)
 		return LIBUSB_ERROR_INVALID_PARAM;
 
-	if (!dev_handle->dev->attached)
+	if (!usbi_atomic_load(&dev_handle->dev->attached))
 		return LIBUSB_ERROR_NO_DEVICE;
 
 	if (usbi_backend.free_streams)
@@ -1933,7 +1929,7 @@
 unsigned char * LIBUSB_CALL libusb_dev_mem_alloc(libusb_device_handle *dev_handle,
         size_t length)
 {
-	if (!dev_handle->dev->attached)
+	if (!usbi_atomic_load(&dev_handle->dev->attached))
 		return NULL;
 
 	if (usbi_backend.dev_mem_alloc)
@@ -1979,12 +1975,12 @@
 int API_EXPORTED libusb_kernel_driver_active(libusb_device_handle *dev_handle,
 	int interface_number)
 {
-	usbi_dbg("interface %d", interface_number);
+	usbi_dbg(HANDLE_CTX(dev_handle), "interface %d", interface_number);
 
 	if (interface_number < 0 || interface_number >= USB_MAXINTERFACES)
 		return LIBUSB_ERROR_INVALID_PARAM;
 
-	if (!dev_handle->dev->attached)
+	if (!usbi_atomic_load(&dev_handle->dev->attached))
 		return LIBUSB_ERROR_NO_DEVICE;
 
 	if (usbi_backend.kernel_driver_active)
@@ -1997,7 +1993,7 @@
  * Detach a kernel driver from an interface. If successful, you will then be
  * able to claim the interface and perform I/O.
  *
- * This functionality is not available on Darwin or Windows.
+ * This functionality is not available on Windows.
  *
  * Note that libusb itself also talks to the device through a special kernel
  * driver, if this driver is already attached to the device, this call will
@@ -2017,12 +2013,12 @@
 int API_EXPORTED libusb_detach_kernel_driver(libusb_device_handle *dev_handle,
 	int interface_number)
 {
-	usbi_dbg("interface %d", interface_number);
+	usbi_dbg(HANDLE_CTX(dev_handle), "interface %d", interface_number);
 
 	if (interface_number < 0 || interface_number >= USB_MAXINTERFACES)
 		return LIBUSB_ERROR_INVALID_PARAM;
 
-	if (!dev_handle->dev->attached)
+	if (!usbi_atomic_load(&dev_handle->dev->attached))
 		return LIBUSB_ERROR_NO_DEVICE;
 
 	if (usbi_backend.detach_kernel_driver)
@@ -2033,10 +2029,9 @@
 
 /** \ingroup libusb_dev
  * Re-attach an interface's kernel driver, which was previously detached
- * using libusb_detach_kernel_driver(). This call is only effective on
- * Linux and returns LIBUSB_ERROR_NOT_SUPPORTED on all other platforms.
+ * using libusb_detach_kernel_driver().
  *
- * This functionality is not available on Darwin or Windows.
+ * This functionality is not available on Windows.
  *
  * \param dev_handle a device handle
  * \param interface_number the interface to attach the driver from
@@ -2054,12 +2049,12 @@
 int API_EXPORTED libusb_attach_kernel_driver(libusb_device_handle *dev_handle,
 	int interface_number)
 {
-	usbi_dbg("interface %d", interface_number);
+	usbi_dbg(HANDLE_CTX(dev_handle), "interface %d", interface_number);
 
 	if (interface_number < 0 || interface_number >= USB_MAXINTERFACES)
 		return LIBUSB_ERROR_INVALID_PARAM;
 
-	if (!dev_handle->dev->attached)
+	if (!usbi_atomic_load(&dev_handle->dev->attached))
 		return LIBUSB_ERROR_NO_DEVICE;
 
 	if (usbi_backend.attach_kernel_driver)
@@ -2130,6 +2125,8 @@
  * If ENABLE_DEBUG_LOGGING is defined then per context callback function will
  * never be called.
  *
+ * Since version 1.0.23, \ref LIBUSB_API_VERSION >= 0x01000107
+ *
  * \param ctx context on which to assign log handler, or NULL for the default
  * context. Parameter ignored if only LIBUSB_LOG_CB_GLOBAL mode is requested.
  * \param cb pointer to the callback function, or NULL to stop log
@@ -2170,6 +2167,9 @@
  * Some options require one or more arguments to be provided. Consult each
  * option's documentation for specific requirements.
  *
+ * If the context ctx is NULL, the option will be added to a list of default
+ * options that will be applied to all subsequently created contexts.
+ *
  * Since version 1.0.22, \ref LIBUSB_API_VERSION >= 0x01000106
  *
  * \param ctx context on which to operate
@@ -2185,40 +2185,63 @@
 int API_EXPORTED libusb_set_option(libusb_context *ctx,
 	enum libusb_option option, ...)
 {
-	int arg, r = LIBUSB_SUCCESS;
+	int arg = 0, r = LIBUSB_SUCCESS;
 	va_list ap;
 
-	ctx = usbi_get_context(ctx);
-
 	va_start(ap, option);
-	switch (option) {
-	case LIBUSB_OPTION_LOG_LEVEL:
+	if (LIBUSB_OPTION_LOG_LEVEL == option) {
 		arg = va_arg(ap, int);
 		if (arg < LIBUSB_LOG_LEVEL_NONE || arg > LIBUSB_LOG_LEVEL_DEBUG) {
 			r = LIBUSB_ERROR_INVALID_PARAM;
-			break;
 		}
+	}
+	va_end(ap);
+
+	if (LIBUSB_SUCCESS != r) {
+		return r;
+	}
+
+	if (option >= LIBUSB_OPTION_MAX) {
+		return LIBUSB_ERROR_INVALID_PARAM;
+	}
+
+	if (NULL == ctx) {
+		usbi_mutex_static_lock(&default_context_lock);
+		default_context_options[option].is_set = 1;
+		if (LIBUSB_OPTION_LOG_LEVEL == option) {
+			default_context_options[option].arg.ival = arg;
+		}
+		usbi_mutex_static_unlock(&default_context_lock);
+	}
+
+	ctx = usbi_get_context(ctx);
+	if (NULL == ctx) {
+		return LIBUSB_SUCCESS;
+	}
+
+	switch (option) {
+	case LIBUSB_OPTION_LOG_LEVEL:
 #if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING)
 		if (!ctx->debug_fixed)
 			ctx->debug = (enum libusb_log_level)arg;
 #endif
 		break;
 
-	/* Handle all backend-specific options here */
+		/* Handle all backend-specific options here */
 	case LIBUSB_OPTION_USE_USBDK:
-	case LIBUSB_OPTION_WEAK_AUTHORITY:
+	case LIBUSB_OPTION_NO_DEVICE_DISCOVERY:
 		if (usbi_backend.set_option)
-			r = usbi_backend.set_option(ctx, option, ap);
-		else
-			r = LIBUSB_ERROR_NOT_SUPPORTED;
+			return usbi_backend.set_option(ctx, option, ap);
+
+		return LIBUSB_ERROR_NOT_SUPPORTED;
 		break;
 
+	case LIBUSB_OPTION_MAX:
 	default:
-		r = LIBUSB_ERROR_INVALID_PARAM;
+		return LIBUSB_ERROR_INVALID_PARAM;
 	}
-	va_end(ap);
 
-	return r;
+	return LIBUSB_SUCCESS;;
 }
 
 #if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING)
@@ -2249,113 +2272,125 @@
  * context will be created. If there was already a default context, it will
  * be reused (and nothing will be initialized/reinitialized).
  *
- * \param context Optional output location for context pointer.
+ * \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
  */
-int API_EXPORTED libusb_init(libusb_context **context)
+int API_EXPORTED libusb_init(libusb_context **ctx)
 {
-	struct libusb_device *dev, *next;
 	size_t priv_size = usbi_backend.context_priv_size;
-	struct libusb_context *ctx;
-	static int first_init = 1;
-	int r = 0;
+	struct libusb_context *_ctx;
+	int r;
 
 	usbi_mutex_static_lock(&default_context_lock);
 
-	if (!timestamp_origin.tv_sec)
-		usbi_get_monotonic_time(&timestamp_origin);
-
-	if (!context && usbi_default_context) {
-		usbi_dbg("reusing default context");
+	if (!ctx && default_context_refcnt > 0) {
+		usbi_dbg(usbi_default_context, "reusing default context");
 		default_context_refcnt++;
 		usbi_mutex_static_unlock(&default_context_lock);
 		return 0;
 	}
 
-	ctx = calloc(1, PTR_ALIGN(sizeof(*ctx)) + priv_size);
-	if (!ctx) {
-		r = LIBUSB_ERROR_NO_MEM;
-		goto err_unlock;
+	/* check for first init */
+	if (!active_contexts_list.next) {
+		list_init(&active_contexts_list);
+		usbi_get_monotonic_time(&timestamp_origin);
+	}
+
+	_ctx = calloc(1, PTR_ALIGN(sizeof(*_ctx)) + priv_size);
+	if (!_ctx) {
+		usbi_mutex_static_unlock(&default_context_lock);
+		return LIBUSB_ERROR_NO_MEM;
 	}
 
 #if defined(ENABLE_LOGGING) && !defined(ENABLE_DEBUG_LOGGING)
-	ctx->debug = get_env_debug_level();
-	if (ctx->debug != LIBUSB_LOG_LEVEL_NONE)
-		ctx->debug_fixed = 1;
+	if (NULL == ctx && default_context_options[LIBUSB_OPTION_LOG_LEVEL].is_set) {
+		_ctx->debug = default_context_options[LIBUSB_OPTION_LOG_LEVEL].arg.ival;
+	} else {
+		_ctx->debug = get_env_debug_level();
+	}
+	if (_ctx->debug != LIBUSB_LOG_LEVEL_NONE)
+		_ctx->debug_fixed = 1;
 #endif
 
-	/* default context should be initialized before calling usbi_dbg */
-	if (!usbi_default_context) {
-		usbi_default_context = ctx;
-		default_context_refcnt++;
-		usbi_dbg("created default context");
-	}
+	usbi_mutex_init(&_ctx->usb_devs_lock);
+	usbi_mutex_init(&_ctx->open_devs_lock);
+	list_init(&_ctx->usb_devs);
+	list_init(&_ctx->open_devs);
 
-	usbi_dbg("libusb v%u.%u.%u.%u%s", libusb_version_internal.major, libusb_version_internal.minor,
-		libusb_version_internal.micro, libusb_version_internal.nano, libusb_version_internal.rc);
-
-	usbi_mutex_init(&ctx->usb_devs_lock);
-	usbi_mutex_init(&ctx->open_devs_lock);
-	usbi_mutex_init(&ctx->hotplug_cbs_lock);
-	list_init(&ctx->usb_devs);
-	list_init(&ctx->open_devs);
-	list_init(&ctx->hotplug_cbs);
-	ctx->next_hotplug_cb_handle = 1;
-
-	usbi_mutex_static_lock(&active_contexts_lock);
-	if (first_init) {
-		first_init = 0;
-		list_init(&active_contexts_list);
-	}
-	list_add (&ctx->list, &active_contexts_list);
-	usbi_mutex_static_unlock(&active_contexts_lock);
-
-	if (usbi_backend.init) {
-		r = usbi_backend.init(ctx);
-		if (r)
+	/* apply default options to all new contexts */
+	for (enum libusb_option option = 0 ; option < LIBUSB_OPTION_MAX ; option++) {
+		if (LIBUSB_OPTION_LOG_LEVEL == option || !default_context_options[option].is_set) {
+			continue;
+		}
+		r = libusb_set_option(_ctx, option);
+		if (LIBUSB_SUCCESS != r)
 			goto err_free_ctx;
 	}
 
-	r = usbi_io_init(ctx);
+	/* default context must be initialized before calling usbi_dbg */
+	if (!ctx) {
+		usbi_default_context = _ctx;
+		default_context_refcnt = 1;
+		usbi_dbg(usbi_default_context, "created default context");
+	}
+
+	usbi_dbg(_ctx, "libusb v%u.%u.%u.%u%s", libusb_version_internal.major, libusb_version_internal.minor,
+		libusb_version_internal.micro, libusb_version_internal.nano, libusb_version_internal.rc);
+
+	r = usbi_io_init(_ctx);
 	if (r < 0)
-		goto err_backend_exit;
+		goto err_free_ctx;
+
+	usbi_mutex_static_lock(&active_contexts_lock);
+	list_add(&_ctx->list, &active_contexts_list);
+	usbi_mutex_static_unlock(&active_contexts_lock);
+
+	if (usbi_backend.init) {
+		r = usbi_backend.init(_ctx);
+		if (r)
+			goto err_io_exit;
+	}
+
+	/* Initialize hotplug after the initial enumeration is done. */
+	usbi_hotplug_init(_ctx);
+
+	if (ctx) {
+		*ctx = _ctx;
+
+		if (!usbi_fallback_context) {
+			usbi_fallback_context = _ctx;
+			usbi_warn(usbi_fallback_context, "installing new context as implicit default");
+		}
+	}
 
 	usbi_mutex_static_unlock(&default_context_lock);
 
-	if (context)
-		*context = ctx;
-
 	return 0;
 
-err_backend_exit:
-	if (usbi_backend.exit)
-		usbi_backend.exit(ctx);
-err_free_ctx:
-	if (ctx == usbi_default_context) {
-		usbi_default_context = NULL;
-		default_context_refcnt--;
-	}
-
+err_io_exit:
 	usbi_mutex_static_lock(&active_contexts_lock);
-	list_del(&ctx->list);
+	list_del(&_ctx->list);
 	usbi_mutex_static_unlock(&active_contexts_lock);
 
-	usbi_mutex_lock(&ctx->usb_devs_lock);
-	for_each_device_safe(ctx, dev, next) {
-		list_del(&dev->list);
-		libusb_unref_device(dev);
+	usbi_hotplug_exit(_ctx);
+	usbi_io_exit(_ctx);
+
+err_free_ctx:
+	if (!ctx) {
+		/* clear default context that was not fully initialized */
+		usbi_default_context = NULL;
+		default_context_refcnt = 0;
 	}
-	usbi_mutex_unlock(&ctx->usb_devs_lock);
 
-	usbi_mutex_destroy(&ctx->open_devs_lock);
-	usbi_mutex_destroy(&ctx->usb_devs_lock);
-	usbi_mutex_destroy(&ctx->hotplug_cbs_lock);
+	usbi_mutex_destroy(&_ctx->open_devs_lock);
+	usbi_mutex_destroy(&_ctx->usb_devs_lock);
 
-	free(ctx);
-err_unlock:
+	free(_ctx);
+
 	usbi_mutex_static_unlock(&default_context_lock);
+
 	return r;
 }
 
@@ -2366,90 +2401,66 @@
  */
 void API_EXPORTED libusb_exit(libusb_context *ctx)
 {
-	struct libusb_device *dev, *next;
-	struct timeval tv = { 0, 0 };
-	int destroying_default_context = 0;
+	struct libusb_context *_ctx;
+	struct libusb_device *dev;
 
-	usbi_dbg(" ");
-
-	ctx = usbi_get_context(ctx);
+	usbi_mutex_static_lock(&default_context_lock);
 
 	/* if working with default context, only actually do the deinitialization
 	 * if we're the last user */
-	usbi_mutex_static_lock(&default_context_lock);
-	if (ctx == usbi_default_context) {
+	if (!ctx) {
 		if (!usbi_default_context) {
-			usbi_dbg("no default context, not initialized?");
+			usbi_dbg(ctx, "no default context, not initialized?");
 			usbi_mutex_static_unlock(&default_context_lock);
 			return;
 		}
 
 		if (--default_context_refcnt > 0) {
-			usbi_dbg("not destroying default context");
+			usbi_dbg(ctx, "not destroying default context");
 			usbi_mutex_static_unlock(&default_context_lock);
 			return;
 		}
-		usbi_dbg("destroying default context");
 
-		/*
-		 * Setting this flag without unlocking the default context, as
-		 * we are actually destroying the default context.
-		 * usbi_default_context is not set to NULL yet, as all activities
-		 * would only stop after usbi_backend->exit() returns.
-		 */
-		destroying_default_context = 1;
+		usbi_dbg(ctx, "destroying default context");
+		_ctx = usbi_default_context;
 	} else {
-		/* Unlock default context, as we're not modifying it. */
-		usbi_mutex_static_unlock(&default_context_lock);
+		usbi_dbg(ctx, " ");
+		_ctx = ctx;
 	}
 
 	usbi_mutex_static_lock(&active_contexts_lock);
-	list_del(&ctx->list);
+	list_del(&_ctx->list);
 	usbi_mutex_static_unlock(&active_contexts_lock);
 
-	if (libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) {
-		usbi_hotplug_deregister(ctx, 1);
-
-		/*
-		 * Ensure any pending unplug events are read from the hotplug
-		 * pipe. The usb_device-s hold in the events are no longer part
-		 * of usb_devs, but the events still hold a reference!
-		 *
-		 * Note we don't do this if the application has left devices
-		 * open (which implies a buggy app) to avoid packet completion
-		 * handlers running when the app does not expect them to run.
-		 */
-		if (list_empty(&ctx->open_devs))
-			libusb_handle_events_timeout(ctx, &tv);
-
-		usbi_mutex_lock(&ctx->usb_devs_lock);
-		for_each_device_safe(ctx, dev, next) {
-			list_del(&dev->list);
-			libusb_unref_device(dev);
-		}
-		usbi_mutex_unlock(&ctx->usb_devs_lock);
-	}
-
-	/* a few sanity checks. don't bother with locking because unless
-	 * there is an application bug, nobody will be accessing these. */
-	if (!list_empty(&ctx->usb_devs))
-		usbi_warn(ctx, "some libusb_devices were leaked");
-	if (!list_empty(&ctx->open_devs))
-		usbi_warn(ctx, "application left some devices open");
-
-	usbi_io_exit(ctx);
 	if (usbi_backend.exit)
-		usbi_backend.exit(ctx);
+		usbi_backend.exit(_ctx);
 
-	usbi_mutex_destroy(&ctx->open_devs_lock);
-	usbi_mutex_destroy(&ctx->usb_devs_lock);
-	usbi_mutex_destroy(&ctx->hotplug_cbs_lock);
-	free(ctx);
-
-	if (destroying_default_context) {
+	if (!ctx)
 		usbi_default_context = NULL;
-		usbi_mutex_static_unlock(&default_context_lock);
+	if (ctx == usbi_fallback_context)
+		usbi_fallback_context = NULL;
+
+	usbi_mutex_static_unlock(&default_context_lock);
+
+	/* Don't bother with locking after this point because unless there is
+	 * an application bug, nobody will be accessing the context. */
+
+	usbi_hotplug_exit(_ctx);
+	usbi_io_exit(_ctx);
+
+	for_each_device(_ctx, dev) {
+		usbi_warn(_ctx, "device %d.%d still referenced",
+			dev->bus_number, dev->device_address);
+		DEVICE_CTX(dev) = NULL;
 	}
+
+	if (!list_empty(&_ctx->open_devs))
+		usbi_warn(_ctx, "application left some devices open");
+
+	usbi_mutex_destroy(&_ctx->open_devs_lock);
+	usbi_mutex_destroy(&_ctx->usb_devs_lock);
+
+	free(_ctx);
 }
 
 /** \ingroup libusb_misc
@@ -2574,7 +2585,8 @@
 #else
 	enum libusb_log_level ctx_level;
 
-	ctx = usbi_get_context(ctx);
+	ctx = ctx ? ctx : usbi_default_context;
+	ctx = ctx ? ctx : usbi_fallback_context;
 	if (ctx)
 		ctx_level = ctx->debug;
 	else
diff --git a/libusb/descriptor.c b/libusb/descriptor.c
index ecd9441..253ef1c 100644
--- a/libusb/descriptor.c
+++ b/libusb/descriptor.c
@@ -140,7 +140,7 @@
 		    header->bDescriptorType == LIBUSB_DT_DEVICE)
 			break;
 
-		usbi_dbg("skipping descriptor 0x%x", header->bDescriptorType);
+		usbi_dbg(ctx, "skipping descriptor 0x%x", header->bDescriptorType);
 		buffer += header->bLength;
 		size -= header->bLength;
 		parsed += header->bLength;
@@ -414,7 +414,7 @@
 			    header->bDescriptorType == LIBUSB_DT_DEVICE)
 				break;
 
-			usbi_dbg("skipping descriptor 0x%x", header->bDescriptorType);
+			usbi_dbg(ctx, "skipping descriptor 0x%x", header->bDescriptorType);
 			buffer += header->bLength;
 			size -= header->bLength;
 		}
@@ -531,7 +531,7 @@
 int API_EXPORTED libusb_get_device_descriptor(libusb_device *dev,
 	struct libusb_device_descriptor *desc)
 {
-	usbi_dbg(" ");
+	usbi_dbg(DEVICE_CTX(dev), " ");
 	static_assert(sizeof(dev->device_descriptor) == LIBUSB_DT_DEVICE_SIZE,
 		      "struct libusb_device_descriptor is not expected size");
 	*desc = dev->device_descriptor;
@@ -601,7 +601,7 @@
 	uint8_t *buf;
 	int r;
 
-	usbi_dbg("index %u", config_index);
+	usbi_dbg(DEVICE_CTX(dev), "index %u", config_index);
 	if (config_index >= dev->device_descriptor.bNumConfigurations)
 		return LIBUSB_ERROR_NOT_FOUND;
 
@@ -656,7 +656,7 @@
 		return raw_desc_to_config(DEVICE_CTX(dev), buf, r, config);
 	}
 
-	usbi_dbg("value %u", bConfigurationValue);
+	usbi_dbg(DEVICE_CTX(dev), "value %u", bConfigurationValue);
 	for (idx = 0; idx < dev->device_descriptor.bNumConfigurations; idx++) {
 		union usbi_config_desc_buf _config;
 
@@ -850,23 +850,24 @@
 	uint16_t bos_len;
 	uint8_t *bos_data;
 	int r;
+	struct libusb_context *ctx = HANDLE_CTX(dev_handle);
 
 	/* Read the BOS. This generates 2 requests on the bus,
 	 * one for the header, and one for the full BOS */
 	r = libusb_get_descriptor(dev_handle, LIBUSB_DT_BOS, 0, _bos.buf, sizeof(_bos.buf));
 	if (r < 0) {
 		if (r != LIBUSB_ERROR_PIPE)
-			usbi_err(HANDLE_CTX(dev_handle), "failed to read BOS (%d)", r);
+			usbi_err(ctx, "failed to read BOS (%d)", r);
 		return r;
 	}
 	if (r < LIBUSB_DT_BOS_SIZE) {
-		usbi_err(HANDLE_CTX(dev_handle), "short BOS read %d/%d",
+		usbi_err(ctx, "short BOS read %d/%d",
 			 r, LIBUSB_DT_BOS_SIZE);
 		return LIBUSB_ERROR_IO;
 	}
 
 	bos_len = libusb_le16_to_cpu(_bos.desc.wTotalLength);
-	usbi_dbg("found BOS descriptor: size %u bytes, %u capabilities",
+	usbi_dbg(ctx, "found BOS descriptor: size %u bytes, %u capabilities",
 		 bos_len, _bos.desc.bNumDeviceCaps);
 	bos_data = calloc(1, bos_len);
 	if (!bos_data)
@@ -875,11 +876,10 @@
 	r = libusb_get_descriptor(dev_handle, LIBUSB_DT_BOS, 0, bos_data, bos_len);
 	if (r >= 0) {
 		if (r != (int)bos_len)
-			usbi_warn(HANDLE_CTX(dev_handle), "short BOS read %d/%u",
-				  r, bos_len);
+			usbi_warn(ctx, "short BOS read %d/%u", r, bos_len);
 		r = parse_bos(HANDLE_CTX(dev_handle), bos, bos_data, r);
 	} else {
-		usbi_err(HANDLE_CTX(dev_handle), "failed to read BOS (%d)", r);
+		usbi_err(ctx, "failed to read BOS (%d)", r);
 	}
 
 	free(bos_data);
@@ -1109,7 +1109,7 @@
 	else if (str.desc.bDescriptorType != LIBUSB_DT_STRING)
 		return LIBUSB_ERROR_IO;
 	else if (str.desc.bLength & 1)
-		usbi_warn(HANDLE_CTX(dev_handle), "suspicious bLength %u for string descriptor", str.desc.bLength);
+		usbi_warn(HANDLE_CTX(dev_handle), "suspicious bLength %u for language ID string descriptor", str.desc.bLength);
 
 	langid = libusb_le16_to_cpu(str.desc.wData[0]);
 	r = libusb_get_string_descriptor(dev_handle, desc_index, langid, str.buf, sizeof(str.buf));
@@ -1120,7 +1120,7 @@
 	else if (str.desc.bDescriptorType != LIBUSB_DT_STRING)
 		return LIBUSB_ERROR_IO;
 	else if ((str.desc.bLength & 1) || str.desc.bLength != r)
-		usbi_warn(HANDLE_CTX(dev_handle), "suspicious bLength %u for string descriptor", str.desc.bLength);
+		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) {
diff --git a/libusb/hotplug.c b/libusb/hotplug.c
index e3e5e76..6b743c7 100644
--- a/libusb/hotplug.c
+++ b/libusb/hotplug.c
@@ -1,7 +1,7 @@
 /* -*- Mode: C; indent-tabs-mode:t ; c-basic-offset:8 -*- */
 /*
  * Hotplug functions for libusb
- * Copyright © 2012-2013 Nathan Hjelm <hjelmn@mac.com>
+ * Copyright © 2012-2021 Nathan Hjelm <hjelmn@mac.com>
  * Copyright © 2012-2013 Peter Stuge <peter@stuge.se>
  *
  * This library is free software; you can redistribute it and/or
@@ -20,7 +20,6 @@
  */
 
 #include "libusbi.h"
-#include "hotplug.h"
 
 /**
  * @defgroup libusb_hotplug Device hotplug event notification
@@ -63,7 +62,7 @@
  * A hotplug event can listen for either or both of these events.
  *
  * Note: If you receive notification that a device has left and you have any
- * a libusb_device_handles for the device it is up to you to call libusb_close()
+ * libusb_device_handles for the device it is up to you to call libusb_close()
  * on each device handle to free up any remaining resources associated with the device.
  * Once a device has left any libusb_device_handle associated with the device
  * are invalid and will remain so even if the device comes back.
@@ -144,15 +143,79 @@
  */
 
 #define VALID_HOTPLUG_EVENTS			\
-	 (LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED |	\
-	  LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT)
+	(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED |	\
+	 LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT)
 
 #define VALID_HOTPLUG_FLAGS			\
-	 (LIBUSB_HOTPLUG_ENUMERATE)
+	(LIBUSB_HOTPLUG_ENUMERATE)
 
-static int usbi_hotplug_match_cb(struct libusb_context *ctx,
-	struct libusb_device *dev, libusb_hotplug_event event,
-	struct libusb_hotplug_callback *hotplug_cb)
+void usbi_hotplug_init(struct libusb_context *ctx)
+{
+	/* check for hotplug support */
+	if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG))
+		return;
+
+	usbi_mutex_init(&ctx->hotplug_cbs_lock);
+	list_init(&ctx->hotplug_cbs);
+	ctx->next_hotplug_cb_handle = 1;
+	usbi_atomic_store(&ctx->hotplug_ready, 1);
+}
+
+void usbi_hotplug_exit(struct libusb_context *ctx)
+{
+	struct usbi_hotplug_callback *hotplug_cb, *next_cb;
+	struct usbi_hotplug_message *msg;
+	struct libusb_device *dev, *next_dev;
+
+	/* check for hotplug support */
+	if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG))
+		return;
+
+	if (!usbi_atomic_load(&ctx->hotplug_ready))
+		return;
+
+	/* free all registered hotplug callbacks */
+	for_each_hotplug_cb_safe(ctx, hotplug_cb, next_cb) {
+		list_del(&hotplug_cb->list);
+		free(hotplug_cb);
+	}
+
+	/* free all pending hotplug messages */
+	while (!list_empty(&ctx->hotplug_msgs)) {
+		msg = list_first_entry(&ctx->hotplug_msgs, struct usbi_hotplug_message, list);
+
+		/* if the device left, the message holds a reference
+		 * and we must drop it */
+		if (msg->event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT)
+			libusb_unref_device(msg->device);
+
+		list_del(&msg->list);
+		free(msg);
+	}
+
+	/* free all discovered devices. due to parent references loop until no devices are freed. */
+	for_each_device_safe(ctx, dev, next_dev) {
+		/* remove the device from the usb_devs list only if there are no
+		 * references held, otherwise leave it on the list so that a
+		 * warning message will be shown */
+		if (usbi_atomic_load(&dev->refcnt) == 1) {
+			list_del(&dev->list);
+		}
+		if (dev->parent_dev && usbi_atomic_load(&dev->parent_dev->refcnt) == 1) {
+			/* the parent was before this device in the list and will be released.
+			   remove it from the list. this is safe as parent_dev can not be
+			   equal to next_dev. */
+			assert (dev->parent_dev != next_dev);
+			list_del(&dev->parent_dev->list);
+		}
+		libusb_unref_device(dev);
+	}
+
+	usbi_mutex_destroy(&ctx->hotplug_cbs_lock);
+}
+
+static int usbi_hotplug_match_cb(struct libusb_device *dev,
+	libusb_hotplug_event event, struct usbi_hotplug_callback *hotplug_cb)
 {
 	if (!(hotplug_cb->flags & event)) {
 		return 0;
@@ -173,28 +236,82 @@
 		return 0;
 	}
 
-	return hotplug_cb->cb(ctx, dev, event, hotplug_cb->user_data);
+	return hotplug_cb->cb(DEVICE_CTX(dev), dev, event, hotplug_cb->user_data);
 }
 
-void usbi_hotplug_match(struct libusb_context *ctx, struct libusb_device *dev,
+void usbi_hotplug_notification(struct libusb_context *ctx, struct libusb_device *dev,
 	libusb_hotplug_event event)
 {
-	struct libusb_hotplug_callback *hotplug_cb, *next;
-	int ret;
+	struct usbi_hotplug_message *msg;
+	unsigned int event_flags;
+
+	/* Only generate a notification if hotplug is ready. This prevents hotplug
+	 * notifications from being generated during initial enumeration or if the
+	 * backend does not support hotplug. */
+	if (!usbi_atomic_load(&ctx->hotplug_ready))
+		return;
+
+	msg = calloc(1, sizeof(*msg));
+	if (!msg) {
+		usbi_err(ctx, "error allocating hotplug message");
+		return;
+	}
+
+	msg->event = event;
+	msg->device = dev;
+
+	/* Take the event data lock and add this message to the list.
+	 * Only signal an event if there are no prior pending events. */
+	usbi_mutex_lock(&ctx->event_data_lock);
+	event_flags = ctx->event_flags;
+	ctx->event_flags |= USBI_EVENT_HOTPLUG_MSG_PENDING;
+	list_add_tail(&msg->list, &ctx->hotplug_msgs);
+	if (!event_flags)
+		usbi_signal_event(&ctx->event);
+	usbi_mutex_unlock(&ctx->event_data_lock);
+}
+
+void usbi_hotplug_process(struct libusb_context *ctx, struct list_head *hotplug_msgs)
+{
+	struct usbi_hotplug_callback *hotplug_cb, *next_cb;
+	struct usbi_hotplug_message *msg;
+	int r;
 
 	usbi_mutex_lock(&ctx->hotplug_cbs_lock);
 
-	for_each_hotplug_cb_safe(ctx, hotplug_cb, next) {
-		if (hotplug_cb->flags & USBI_HOTPLUG_NEEDS_FREE) {
-			/* process deregistration in usbi_hotplug_deregister() */
-			continue;
+	/* dispatch all pending hotplug messages */
+	while (!list_empty(hotplug_msgs)) {
+		msg = list_first_entry(hotplug_msgs, struct usbi_hotplug_message, list);
+
+		for_each_hotplug_cb_safe(ctx, hotplug_cb, next_cb) {
+			/* skip callbacks that have unregistered */
+			if (hotplug_cb->flags & USBI_HOTPLUG_NEEDS_FREE)
+				continue;
+
+			usbi_mutex_unlock(&ctx->hotplug_cbs_lock);
+			r = usbi_hotplug_match_cb(msg->device, msg->event, hotplug_cb);
+			usbi_mutex_lock(&ctx->hotplug_cbs_lock);
+
+			if (r) {
+				list_del(&hotplug_cb->list);
+				free(hotplug_cb);
+			}
 		}
 
-		usbi_mutex_unlock(&ctx->hotplug_cbs_lock);
-		ret = usbi_hotplug_match_cb(ctx, dev, event, hotplug_cb);
-		usbi_mutex_lock(&ctx->hotplug_cbs_lock);
+		/* if the device left, the message holds a reference
+		 * and we must drop it */
+		if (msg->event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT)
+			libusb_unref_device(msg->device);
 
-		if (ret) {
+		list_del(&msg->list);
+		free(msg);
+	}
+
+	/* free any callbacks that have unregistered */
+	for_each_hotplug_cb_safe(ctx, hotplug_cb, next_cb) {
+		if (hotplug_cb->flags & USBI_HOTPLUG_NEEDS_FREE) {
+			usbi_dbg(ctx, "freeing hotplug cb %p with handle %d",
+				hotplug_cb, hotplug_cb->handle);
 			list_del(&hotplug_cb->list);
 			free(hotplug_cb);
 		}
@@ -203,41 +320,16 @@
 	usbi_mutex_unlock(&ctx->hotplug_cbs_lock);
 }
 
-void usbi_hotplug_notification(struct libusb_context *ctx, struct libusb_device *dev,
-	libusb_hotplug_event event)
-{
-	struct libusb_hotplug_message *message = calloc(1, sizeof(*message));
-	unsigned int event_flags;
-
-	if (!message) {
-		usbi_err(ctx, "error allocating hotplug message");
-		return;
-	}
-
-	message->event = event;
-	message->device = dev;
-
-	/* Take the event data lock and add this message to the list.
-	 * Only signal an event if there are no prior pending events. */
-	usbi_mutex_lock(&ctx->event_data_lock);
-	event_flags = ctx->event_flags;
-	ctx->event_flags |= USBI_EVENT_HOTPLUG_MSG_PENDING;
-	list_add_tail(&message->list, &ctx->hotplug_msgs);
-	if (!event_flags)
-		usbi_signal_event(&ctx->event);
-	usbi_mutex_unlock(&ctx->event_data_lock);
-}
-
 int API_EXPORTED libusb_hotplug_register_callback(libusb_context *ctx,
 	int events, int flags,
 	int vendor_id, int product_id, int dev_class,
 	libusb_hotplug_callback_fn cb_fn, void *user_data,
 	libusb_hotplug_callback_handle *callback_handle)
 {
-	struct libusb_hotplug_callback *new_callback;
+	struct usbi_hotplug_callback *hotplug_cb;
 
 	/* check for sane values */
-	if ((!events || (~VALID_HOTPLUG_EVENTS & events)) ||
+	if (!events || (~VALID_HOTPLUG_EVENTS & events) ||
 	    (~VALID_HOTPLUG_FLAGS & flags) ||
 	    (LIBUSB_HOTPLUG_MATCH_ANY != vendor_id && (~0xffff & vendor_id)) ||
 	    (LIBUSB_HOTPLUG_MATCH_ANY != product_id && (~0xffff & product_id)) ||
@@ -247,47 +339,45 @@
 	}
 
 	/* check for hotplug support */
-	if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) {
+	if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG))
 		return LIBUSB_ERROR_NOT_SUPPORTED;
-	}
 
 	ctx = usbi_get_context(ctx);
 
-	new_callback = calloc(1, sizeof(*new_callback));
-	if (!new_callback) {
+	hotplug_cb = calloc(1, sizeof(*hotplug_cb));
+	if (!hotplug_cb)
 		return LIBUSB_ERROR_NO_MEM;
-	}
 
-	new_callback->flags = (uint8_t)events;
+	hotplug_cb->flags = (uint8_t)events;
 	if (LIBUSB_HOTPLUG_MATCH_ANY != vendor_id) {
-		new_callback->flags |= USBI_HOTPLUG_VENDOR_ID_VALID;
-		new_callback->vendor_id = (uint16_t)vendor_id;
+		hotplug_cb->flags |= USBI_HOTPLUG_VENDOR_ID_VALID;
+		hotplug_cb->vendor_id = (uint16_t)vendor_id;
 	}
 	if (LIBUSB_HOTPLUG_MATCH_ANY != product_id) {
-		new_callback->flags |= USBI_HOTPLUG_PRODUCT_ID_VALID;
-		new_callback->product_id = (uint16_t)product_id;
+		hotplug_cb->flags |= USBI_HOTPLUG_PRODUCT_ID_VALID;
+		hotplug_cb->product_id = (uint16_t)product_id;
 	}
 	if (LIBUSB_HOTPLUG_MATCH_ANY != dev_class) {
-		new_callback->flags |= USBI_HOTPLUG_DEV_CLASS_VALID;
-		new_callback->dev_class = (uint8_t)dev_class;
+		hotplug_cb->flags |= USBI_HOTPLUG_DEV_CLASS_VALID;
+		hotplug_cb->dev_class = (uint8_t)dev_class;
 	}
-	new_callback->cb = cb_fn;
-	new_callback->user_data = user_data;
+	hotplug_cb->cb = cb_fn;
+	hotplug_cb->user_data = user_data;
 
 	usbi_mutex_lock(&ctx->hotplug_cbs_lock);
 
 	/* protect the handle by the context hotplug lock */
-	new_callback->handle = ctx->next_hotplug_cb_handle++;
+	hotplug_cb->handle = ctx->next_hotplug_cb_handle++;
 
 	/* handle the unlikely case of overflow */
 	if (ctx->next_hotplug_cb_handle < 0)
 		ctx->next_hotplug_cb_handle = 1;
 
-	list_add(&new_callback->list, &ctx->hotplug_cbs);
+	list_add(&hotplug_cb->list, &ctx->hotplug_cbs);
 
 	usbi_mutex_unlock(&ctx->hotplug_cbs_lock);
 
-	usbi_dbg("new hotplug cb %p with handle %d", new_callback, new_callback->handle);
+	usbi_dbg(ctx, "new hotplug cb %p with handle %d", hotplug_cb, hotplug_cb->handle);
 
 	if ((flags & LIBUSB_HOTPLUG_ENUMERATE) && (events & LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED)) {
 		ssize_t i, len;
@@ -295,23 +385,21 @@
 
 		len = libusb_get_device_list(ctx, &devs);
 		if (len < 0) {
-			libusb_hotplug_deregister_callback(ctx,
-							new_callback->handle);
+			libusb_hotplug_deregister_callback(ctx, hotplug_cb->handle);
 			return (int)len;
 		}
 
 		for (i = 0; i < len; i++) {
-			usbi_hotplug_match_cb(ctx, devs[i],
+			usbi_hotplug_match_cb(devs[i],
 					LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED,
-					new_callback);
+					hotplug_cb);
 		}
 
 		libusb_free_device_list(devs, 1);
 	}
 
-
 	if (callback_handle)
-		*callback_handle = new_callback->handle;
+		*callback_handle = hotplug_cb->handle;
 
 	return LIBUSB_SUCCESS;
 }
@@ -319,24 +407,24 @@
 void API_EXPORTED libusb_hotplug_deregister_callback(libusb_context *ctx,
 	libusb_hotplug_callback_handle callback_handle)
 {
-	struct libusb_hotplug_callback *hotplug_cb;
+	struct usbi_hotplug_callback *hotplug_cb;
 	int deregistered = 0;
 
 	/* check for hotplug support */
-	if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) {
+	if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG))
 		return;
-	}
 
-	usbi_dbg("deregister hotplug cb %d", callback_handle);
+	usbi_dbg(ctx, "deregister hotplug cb %d", callback_handle);
 
 	ctx = usbi_get_context(ctx);
 
 	usbi_mutex_lock(&ctx->hotplug_cbs_lock);
 	for_each_hotplug_cb(ctx, hotplug_cb) {
 		if (callback_handle == hotplug_cb->handle) {
-			/* Mark this callback for deregistration */
+			/* mark this callback for deregistration */
 			hotplug_cb->flags |= USBI_HOTPLUG_NEEDS_FREE;
 			deregistered = 1;
+			break;
 		}
 	}
 	usbi_mutex_unlock(&ctx->hotplug_cbs_lock);
@@ -357,15 +445,14 @@
 void * LIBUSB_CALL libusb_hotplug_get_user_data(libusb_context *ctx,
 	libusb_hotplug_callback_handle callback_handle)
 {
-	struct libusb_hotplug_callback *hotplug_cb;
+	struct usbi_hotplug_callback *hotplug_cb;
 	void *user_data = NULL;
 
 	/* check for hotplug support */
-	if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) {
+	if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG))
 		return NULL;
-	}
 
-	usbi_dbg("get hotplug user data %d", callback_handle);
+	usbi_dbg(ctx, "get hotplug cb %d user data", callback_handle);
 
 	ctx = usbi_get_context(ctx);
 
@@ -373,25 +460,10 @@
 	for_each_hotplug_cb(ctx, hotplug_cb) {
 		if (callback_handle == hotplug_cb->handle) {
 			user_data = hotplug_cb->user_data;
+			break;
 		}
 	}
 	usbi_mutex_unlock(&ctx->hotplug_cbs_lock);
 
 	return user_data;
 }
-
-void usbi_hotplug_deregister(struct libusb_context *ctx, int forced)
-{
-	struct libusb_hotplug_callback *hotplug_cb, *next;
-
-	usbi_mutex_lock(&ctx->hotplug_cbs_lock);
-	for_each_hotplug_cb_safe(ctx, hotplug_cb, next) {
-		if (forced || (hotplug_cb->flags & USBI_HOTPLUG_NEEDS_FREE)) {
-			usbi_dbg("freeing hotplug cb %p with handle %d", hotplug_cb,
-				 hotplug_cb->handle);
-			list_del(&hotplug_cb->list);
-			free(hotplug_cb);
-		}
-	}
-	usbi_mutex_unlock(&ctx->hotplug_cbs_lock);
-}
diff --git a/libusb/hotplug.h b/libusb/hotplug.h
deleted file mode 100644
index 161f7e5..0000000
--- a/libusb/hotplug.h
+++ /dev/null
@@ -1,105 +0,0 @@
-/* -*- Mode: C; indent-tabs-mode:t ; c-basic-offset:8 -*- */
-/*
- * Hotplug support for libusb
- * Copyright © 2012-2013 Nathan Hjelm <hjelmn@mac.com>
- * Copyright © 2012-2013 Peter Stuge <peter@stuge.se>
- *
- * 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
- */
-
-#ifndef USBI_HOTPLUG_H
-#define USBI_HOTPLUG_H
-
-#include "libusbi.h"
-
-enum usbi_hotplug_flags {
-	/* This callback is interested in device arrivals */
-	USBI_HOTPLUG_DEVICE_ARRIVED = LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED,
-
-	/* This callback is interested in device removals */
-	USBI_HOTPLUG_DEVICE_LEFT = LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
-
-	/* IMPORTANT: The values for the below entries must start *after*
-	 * the highest value of the above entries!!!
-	 */
-
-	/* The vendor_id field is valid for matching */
-	USBI_HOTPLUG_VENDOR_ID_VALID = (1U << 3),
-
-	/* The product_id field is valid for matching */
-	USBI_HOTPLUG_PRODUCT_ID_VALID = (1U << 4),
-
-	/* The dev_class field is valid for matching */
-	USBI_HOTPLUG_DEV_CLASS_VALID = (1U << 5),
-
-	/* This callback has been unregistered and needs to be freed */
-	USBI_HOTPLUG_NEEDS_FREE = (1U << 6),
-};
-
-/** \ingroup hotplug
- * The hotplug callback structure. The user populates this structure with
- * libusb_hotplug_prepare_callback() and then calls libusb_hotplug_register_callback()
- * to receive notification of hotplug events.
- */
-struct libusb_hotplug_callback {
-	/** Flags that control how this callback behaves */
-	uint8_t flags;
-
-	/** Vendor ID to match (if flags says this is valid) */
-	uint16_t vendor_id;
-
-	/** Product ID to match (if flags says this is valid) */
-	uint16_t product_id;
-
-	/** Device class to match (if flags says this is valid) */
-	uint8_t dev_class;
-
-	/** Callback function to invoke for matching event/device */
-	libusb_hotplug_callback_fn cb;
-
-	/** Handle for this callback (used to match on deregister) */
-	libusb_hotplug_callback_handle handle;
-
-	/** User data that will be passed to the callback function */
-	void *user_data;
-
-	/** List this callback is registered in (ctx->hotplug_cbs) */
-	struct list_head list;
-};
-
-struct libusb_hotplug_message {
-	/** The hotplug event that occurred */
-	libusb_hotplug_event event;
-
-	/** The device for which this hotplug event occurred */
-	struct libusb_device *device;
-
-	/** List this message is contained in (ctx->hotplug_msgs) */
-	struct list_head list;
-};
-
-#define for_each_hotplug_cb(ctx, c) \
-	for_each_helper(c, &(ctx)->hotplug_cbs, struct libusb_hotplug_callback)
-
-#define for_each_hotplug_cb_safe(ctx, c, n) \
-	for_each_safe_helper(c, n, &(ctx)->hotplug_cbs, struct libusb_hotplug_callback)
-
-void usbi_hotplug_deregister(struct libusb_context *ctx, int forced);
-void usbi_hotplug_match(struct libusb_context *ctx, struct libusb_device *dev,
-	libusb_hotplug_event event);
-void usbi_hotplug_notification(struct libusb_context *ctx, struct libusb_device *dev,
-	libusb_hotplug_event event);
-
-#endif
diff --git a/libusb/io.c b/libusb/io.c
index 0e960dd..9e3146c 100644
--- a/libusb/io.c
+++ b/libusb/io.c
@@ -22,7 +22,6 @@
  */
 
 #include "libusbi.h"
-#include "hotplug.h"
 
 /**
  * \page libusb_io Synchronous and asynchronous device I/O
@@ -73,7 +72,7 @@
  * a single function call. When the function call returns, the transfer has
  * completed and you can parse the results.
  *
- * If you have used the libusb-0.1 before, this I/O style will seem familiar to
+ * If you have used libusb-0.1 before, this I/O style will seem familiar to
  * you. libusb-0.1 only offered a synchronous interface.
  *
  * In our input device example, to read button presses you might write code
@@ -1181,12 +1180,12 @@
 #ifdef HAVE_OS_TIMER
 	r = usbi_create_timer(&ctx->timer);
 	if (r == 0) {
-		usbi_dbg("using timer for timeouts");
+		usbi_dbg(ctx, "using timer for timeouts");
 		r = usbi_add_event_source(ctx, USBI_TIMER_OS_HANDLE(&ctx->timer), USBI_TIMER_POLL_EVENTS);
 		if (r < 0)
 			goto err_destroy_timer;
 	} else {
-		usbi_dbg("timer not available for timeouts");
+		usbi_dbg(ctx, "timer not available for timeouts");
 	}
 #endif
 
@@ -1310,7 +1309,6 @@
 	itransfer->priv = ptr;
 	usbi_mutex_init(&itransfer->lock);
 	transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
-	usbi_dbg("transfer %p", transfer);
 	return transfer;
 }
 
@@ -1340,12 +1338,14 @@
 	if (!transfer)
 		return;
 
-	usbi_dbg("transfer %p", transfer);
+	usbi_dbg(TRANSFER_CTX(transfer), "transfer %p", transfer);
 	if (transfer->flags & LIBUSB_TRANSFER_FREE_BUFFER)
 		free(transfer->buffer);
 
 	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;
@@ -1376,12 +1376,12 @@
 
 		/* 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("next timeout originally %ums", USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer)->timeout);
+			usbi_dbg(ctx, "next timeout originally %ums", USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer)->timeout);
 			return usbi_arm_timer(&ctx->timer, cur_ts);
 		}
 	}
 
-	usbi_dbg("no timeouts, disarming timer");
+	usbi_dbg(ctx, "no timeouts, disarming timer");
 	return usbi_disarm_timer(&ctx->timer);
 }
 #else
@@ -1438,7 +1438,7 @@
 	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 */
-		usbi_dbg("arm timer for timeout in %ums (first in line)",
+		usbi_dbg(ctx, "arm timer for timeout in %ums (first in line)",
 			USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer)->timeout);
 		r = usbi_arm_timer(&ctx->timer, timeout);
 	}
@@ -1491,10 +1491,16 @@
 {
 	struct usbi_transfer *itransfer =
 		LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer);
-	struct libusb_context *ctx = TRANSFER_CTX(transfer);
+	struct libusb_context *ctx;
 	int r;
 
-	usbi_dbg("transfer %p", transfer);
+	assert(transfer->dev_handle);
+	if (itransfer->dev)
+		libusb_unref_device(itransfer->dev);
+	itransfer->dev = libusb_ref_device(transfer->dev_handle->dev);
+
+	ctx = HANDLE_CTX(transfer->dev_handle);
+	usbi_dbg(ctx, "transfer %p", transfer);
 
 	/*
 	 * Important note on locking, this function takes / releases locks
@@ -1546,15 +1552,13 @@
 	}
 	/*
 	 * We must release the flying transfers lock here, because with
-	 * some backends the submit_transfer method is synchroneous.
+	 * some backends the submit_transfer method is synchronous.
 	 */
 	usbi_mutex_unlock(&ctx->flying_transfers_lock);
 
 	r = usbi_backend.submit_transfer(itransfer);
 	if (r == LIBUSB_SUCCESS) {
 		itransfer->state_flags |= USBI_TRANSFER_IN_FLIGHT;
-		/* keep a reference to this device */
-		libusb_ref_device(transfer->dev_handle->dev);
 	}
 	usbi_mutex_unlock(&itransfer->lock);
 
@@ -1572,6 +1576,26 @@
  * \ref libusb_transfer_status::LIBUSB_TRANSFER_CANCELLED
  * "LIBUSB_TRANSFER_CANCELLED."
  *
+ * This function behaves differently on Darwin-based systems (macOS and iOS):
+ *
+ * - Calling this function for one transfer will cause all transfers on the
+ *   same endpoint to be cancelled. Your callback function will be invoked with
+ *   a transfer status of
+ *   \ref libusb_transfer_status::LIBUSB_TRANSFER_CANCELLED
+ *   "LIBUSB_TRANSFER_CANCELLED" for each transfer that was cancelled.
+
+ * - Calling this function also sends a \c ClearFeature(ENDPOINT_HALT) request
+ *   for the transfer's endpoint. If the device does not handle this request
+ *   correctly, the data toggle bits for the endpoint can be left out of sync
+ *   between host and device, which can have unpredictable results when the
+ *   next data is sent on the endpoint, including data being silently lost.
+ *   A call to \ref libusb_clear_halt will not resolve this situation, since
+ *   that function uses the same request. Therefore, if your program runs on
+ *   Darwin and uses a device that does not correctly implement
+ *   \c ClearFeature(ENDPOINT_HALT) requests, it may only be safe to cancel
+ *   transfers when followed by a device reset using
+ *   \ref libusb_reset_device.
+ *
  * \param transfer the transfer to cancel
  * \returns 0 on success
  * \returns LIBUSB_ERROR_NOT_FOUND if the transfer is not in progress,
@@ -1582,9 +1606,10 @@
 {
 	struct usbi_transfer *itransfer =
 		LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer);
+	struct libusb_context *ctx = ITRANSFER_CTX(itransfer);
 	int r;
 
-	usbi_dbg("transfer %p", transfer );
+	usbi_dbg(ctx, "transfer %p", transfer );
 	usbi_mutex_lock(&itransfer->lock);
 	if (!(itransfer->state_flags & USBI_TRANSFER_IN_FLIGHT)
 			|| (itransfer->state_flags & USBI_TRANSFER_CANCELLING)) {
@@ -1595,10 +1620,9 @@
 	if (r < 0) {
 		if (r != LIBUSB_ERROR_NOT_FOUND &&
 		    r != LIBUSB_ERROR_NO_DEVICE)
-			usbi_err(TRANSFER_CTX(transfer),
-				"cancel transfer failed error %d", r);
+			usbi_err(ctx, "cancel transfer failed error %d", r);
 		else
-			usbi_dbg("cancel transfer failed error %d", r);
+			usbi_dbg(ctx, "cancel transfer failed error %d", r);
 
 		if (r == LIBUSB_ERROR_NO_DEVICE)
 			itransfer->state_flags |= USBI_TRANSFER_DEVICE_DISAPPEARED;
@@ -1661,13 +1685,13 @@
 {
 	struct libusb_transfer *transfer =
 		USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
-	struct libusb_device_handle *dev_handle = transfer->dev_handle;
+	struct libusb_context *ctx = ITRANSFER_CTX(itransfer);
 	uint8_t flags;
 	int r;
 
 	r = remove_from_flying_list(itransfer);
 	if (r < 0)
-		usbi_err(ITRANSFER_CTX(itransfer), "failed to set timer for next timeout");
+		usbi_err(ctx, "failed to set timer for next timeout");
 
 	usbi_mutex_lock(&itransfer->lock);
 	itransfer->state_flags &= ~USBI_TRANSFER_IN_FLIGHT;
@@ -1679,7 +1703,7 @@
 		if (transfer->type == LIBUSB_TRANSFER_TYPE_CONTROL)
 			rqlen -= LIBUSB_CONTROL_SETUP_SIZE;
 		if (rqlen != itransfer->transferred) {
-			usbi_dbg("interpreting short transfer as error");
+			usbi_dbg(ctx, "interpreting short transfer as error");
 			status = LIBUSB_TRANSFER_ERROR;
 		}
 	}
@@ -1687,14 +1711,13 @@
 	flags = transfer->flags;
 	transfer->status = status;
 	transfer->actual_length = itransfer->transferred;
-	usbi_dbg("transfer %p has callback %p", transfer, transfer->callback);
+	usbi_dbg(ctx, "transfer %p has callback %p", transfer, transfer->callback);
 	if (transfer->callback)
 		transfer->callback(transfer);
 	/* transfer might have been freed by the above call, do not use from
 	 * this point. */
 	if (flags & LIBUSB_TRANSFER_FREE_TRANSFER)
 		libusb_free_transfer(transfer);
-	libusb_unref_device(dev_handle->dev);
 	return r;
 }
 
@@ -1715,7 +1738,7 @@
 
 	/* if the URB was cancelled due to timeout, report timeout to the user */
 	if (timed_out) {
-		usbi_dbg("detected timeout cancellation");
+		usbi_dbg(ctx, "detected timeout cancellation");
 		return usbi_handle_transfer_completion(itransfer, LIBUSB_TRANSFER_TIMED_OUT);
 	}
 
@@ -1728,10 +1751,10 @@
  * function will be called the next time an event handler runs. */
 void usbi_signal_transfer_completion(struct usbi_transfer *itransfer)
 {
-	libusb_device_handle *dev_handle = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer)->dev_handle;
+	struct libusb_device *dev = itransfer->dev;
 
-	if (dev_handle) {
-		struct libusb_context *ctx = HANDLE_CTX(dev_handle);
+	if (dev) {
+		struct libusb_context *ctx = DEVICE_CTX(dev);
 		unsigned int event_flags;
 
 		usbi_mutex_lock(&ctx->event_data_lock);
@@ -1776,7 +1799,7 @@
 	ru = ctx->device_close;
 	usbi_mutex_unlock(&ctx->event_data_lock);
 	if (ru) {
-		usbi_dbg("someone else is closing a device");
+		usbi_dbg(ctx, "someone else is closing a device");
 		return 1;
 	}
 
@@ -1868,7 +1891,7 @@
 	r = ctx->device_close;
 	usbi_mutex_unlock(&ctx->event_data_lock);
 	if (r) {
-		usbi_dbg("someone else is closing a device");
+		usbi_dbg(ctx, "someone else is closing a device");
 		return 0;
 	}
 
@@ -1897,7 +1920,7 @@
 	r = ctx->device_close;
 	usbi_mutex_unlock(&ctx->event_data_lock);
 	if (r) {
-		usbi_dbg("someone else is closing a device");
+		usbi_dbg(ctx, "someone else is closing a device");
 		return 1;
 	}
 
@@ -1918,7 +1941,7 @@
 {
 	unsigned int event_flags;
 
-	usbi_dbg(" ");
+	usbi_dbg(ctx, " ");
 
 	ctx = usbi_get_context(ctx);
 	usbi_mutex_lock(&ctx->event_data_lock);
@@ -2073,9 +2096,10 @@
 static int handle_event_trigger(struct libusb_context *ctx)
 {
 	struct list_head hotplug_msgs;
+	int hotplug_event = 0;
 	int r = 0;
 
-	usbi_dbg("event triggered");
+	usbi_dbg(ctx, "event triggered");
 
 	list_init(&hotplug_msgs);
 
@@ -2084,21 +2108,28 @@
 
 	/* check if someone modified the event sources */
 	if (ctx->event_flags & USBI_EVENT_EVENT_SOURCES_MODIFIED)
-		usbi_dbg("someone updated the event sources");
+		usbi_dbg(ctx, "someone updated the event sources");
 
 	if (ctx->event_flags & USBI_EVENT_USER_INTERRUPT) {
-		usbi_dbg("someone purposefully interrupted");
+		usbi_dbg(ctx, "someone purposefully interrupted");
 		ctx->event_flags &= ~USBI_EVENT_USER_INTERRUPT;
 	}
 
+	if (ctx->event_flags & USBI_EVENT_HOTPLUG_CB_DEREGISTERED) {
+		usbi_dbg(ctx, "someone unregistered a hotplug cb");
+		ctx->event_flags &= ~USBI_EVENT_HOTPLUG_CB_DEREGISTERED;
+		hotplug_event = 1;
+	}
+
 	/* check if someone is closing a device */
 	if (ctx->event_flags & USBI_EVENT_DEVICE_CLOSE)
-		usbi_dbg("someone is closing a device");
+		usbi_dbg(ctx, "someone is closing a device");
 
 	/* check for any pending hotplug messages */
 	if (ctx->event_flags & USBI_EVENT_HOTPLUG_MSG_PENDING) {
-		usbi_dbg("hotplug message received");
+		usbi_dbg(ctx, "hotplug message received");
 		ctx->event_flags &= ~USBI_EVENT_HOTPLUG_MSG_PENDING;
+		hotplug_event = 1;
 		assert(!list_empty(&ctx->hotplug_msgs));
 		list_cut(&hotplug_msgs, &ctx->hotplug_msgs);
 	}
@@ -2136,20 +2167,9 @@
 
 	usbi_mutex_unlock(&ctx->event_data_lock);
 
-	/* process the hotplug messages, if any */
-	while (!list_empty(&hotplug_msgs)) {
-		struct libusb_hotplug_message *message =
-			list_first_entry(&hotplug_msgs, struct libusb_hotplug_message, list);
-
-		usbi_hotplug_match(ctx, message->device, message->event);
-
-		/* the device left, dereference the device */
-		if (message->event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT)
-			libusb_unref_device(message->device);
-
-		list_del(&message->list);
-		free(message);
-	}
+	/* process the hotplug events, if any */
+	if (hotplug_event)
+		usbi_hotplug_process(ctx, &hotplug_msgs);
 
 	return r;
 }
@@ -2190,7 +2210,7 @@
 	 * save the additional overhead */
 	usbi_mutex_lock(&ctx->event_data_lock);
 	if (ctx->event_flags & USBI_EVENT_EVENT_SOURCES_MODIFIED) {
-		usbi_dbg("event sources modified, reallocating event data");
+		usbi_dbg(ctx, "event sources modified, reallocating event data");
 
 		/* free anything removed since we last ran */
 		cleanup_removed_event_sources(ctx);
@@ -2337,7 +2357,7 @@
 	if (libusb_try_lock_events(ctx) == 0) {
 		if (completed == NULL || !*completed) {
 			/* we obtained the event lock: do our own event handling */
-			usbi_dbg("doing our own event handling");
+			usbi_dbg(ctx, "doing our own event handling");
 			r = handle_events(ctx, &poll_timeout);
 		}
 		libusb_unlock_events(ctx);
@@ -2355,11 +2375,11 @@
 		/* we hit a race: whoever was event handling earlier finished in the
 		 * time it took us to reach this point. try the cycle again. */
 		libusb_unlock_event_waiters(ctx);
-		usbi_dbg("event handler was active but went away, retrying");
+		usbi_dbg(ctx, "event handler was active but went away, retrying");
 		goto retry;
 	}
 
-	usbi_dbg("another thread is doing event handling");
+	usbi_dbg(ctx, "another thread is doing event handling");
 	r = libusb_wait_for_event(ctx, &poll_timeout);
 
 already_done:
@@ -2554,7 +2574,7 @@
 	usbi_mutex_lock(&ctx->flying_transfers_lock);
 	if (list_empty(&ctx->flying_transfers)) {
 		usbi_mutex_unlock(&ctx->flying_transfers_lock);
-		usbi_dbg("no URBs, no timeout!");
+		usbi_dbg(ctx, "no URBs, no timeout!");
 		return 0;
 	}
 
@@ -2573,19 +2593,19 @@
 	usbi_mutex_unlock(&ctx->flying_transfers_lock);
 
 	if (!TIMESPEC_IS_SET(&next_timeout)) {
-		usbi_dbg("no URB with timeout or all handled by OS; no timeout!");
+		usbi_dbg(ctx, "no URB with timeout or all handled by OS; no timeout!");
 		return 0;
 	}
 
 	usbi_get_monotonic_time(&systime);
 
 	if (!TIMESPEC_CMP(&systime, &next_timeout, <)) {
-		usbi_dbg("first timeout already expired");
+		usbi_dbg(ctx, "first timeout already expired");
 		timerclear(tv);
 	} else {
 		TIMESPEC_SUB(&next_timeout, &systime, &next_timeout);
 		TIMESPEC_TO_TIMEVAL(tv, &next_timeout);
-		usbi_dbg("next timeout in %ld.%06lds", (long)tv->tv_sec, (long)tv->tv_usec);
+		usbi_dbg(ctx, "next timeout in %ld.%06lds", (long)tv->tv_sec, (long)tv->tv_usec);
 	}
 
 	return 1;
@@ -2656,7 +2676,7 @@
 	if (!ievent_source)
 		return LIBUSB_ERROR_NO_MEM;
 
-	usbi_dbg("add " USBI_OS_HANDLE_FORMAT_STRING " events %d", os_handle, poll_events);
+	usbi_dbg(ctx, "add " USBI_OS_HANDLE_FORMAT_STRING " events %d", os_handle, poll_events);
 	ievent_source->data.os_handle = os_handle;
 	ievent_source->data.poll_events = poll_events;
 	usbi_mutex_lock(&ctx->event_data_lock);
@@ -2678,7 +2698,7 @@
 	struct usbi_event_source *ievent_source;
 	int found = 0;
 
-	usbi_dbg("remove " USBI_OS_HANDLE_FORMAT_STRING, os_handle);
+	usbi_dbg(ctx, "remove " USBI_OS_HANDLE_FORMAT_STRING, os_handle);
 	usbi_mutex_lock(&ctx->event_data_lock);
 	for_each_event_source(ctx, ievent_source) {
 		if (ievent_source->data.os_handle == os_handle) {
@@ -2688,7 +2708,7 @@
 	}
 
 	if (!found) {
-		usbi_dbg("couldn't find " USBI_OS_HANDLE_FORMAT_STRING " to remove", os_handle);
+		usbi_dbg(ctx, "couldn't find " USBI_OS_HANDLE_FORMAT_STRING " to remove", os_handle);
 		usbi_mutex_unlock(&ctx->event_data_lock);
 		return;
 	}
@@ -2787,7 +2807,7 @@
 	struct usbi_transfer *cur;
 	struct usbi_transfer *to_cancel;
 
-	usbi_dbg("device %d.%d",
+	usbi_dbg(ctx, "device %d.%d",
 		dev_handle->dev->bus_number, dev_handle->dev->device_address);
 
 	/* terminate all pending transfers with the LIBUSB_TRANSFER_NO_DEVICE
@@ -2822,7 +2842,7 @@
 		if (!to_cancel)
 			break;
 
-		usbi_dbg("cancelling transfer %p from disconnect",
+		usbi_dbg(ctx, "cancelling transfer %p from disconnect",
 			 USBI_TRANSFER_TO_LIBUSB_TRANSFER(to_cancel));
 
 		usbi_mutex_lock(&to_cancel->lock);
diff --git a/libusb/libusb-1.0.def b/libusb/libusb-1.0.def
index 700a8bc..5c7352e 100644
--- a/libusb/libusb-1.0.def
+++ b/libusb/libusb-1.0.def
@@ -150,12 +150,12 @@
   libusb_set_configuration@8 = libusb_set_configuration
   libusb_set_debug
   libusb_set_debug@8 = libusb_set_debug
-  libusb_set_log_cb
-  libusb_set_log_cb@12 = libusb_set_log_cb
   libusb_set_interface_alt_setting
   libusb_set_interface_alt_setting@12 = libusb_set_interface_alt_setting
+  libusb_set_log_cb
+  libusb_set_log_cb@12 = libusb_set_log_cb
   libusb_set_option
-  _libusb_set_option = libusb_set_option
+  libusb_set_option@8 = libusb_set_option
   libusb_set_pollfd_notifiers
   libusb_set_pollfd_notifiers@16 = libusb_set_pollfd_notifiers
   libusb_setlocale
diff --git a/libusb/libusb.h b/libusb/libusb.h
index 1308571..2592ea7 100644
--- a/libusb/libusb.h
+++ b/libusb/libusb.h
@@ -26,13 +26,19 @@
 #define LIBUSB_H
 
 #if defined(_MSC_VER)
+#pragma warning(push)
+/* Disable: warning C4200: nonstandard extension used : zero-sized array in struct/union */
+#pragma warning(disable:4200)
 /* on MS environments, the inline keyword is available in C++ only */
 #if !defined(__cplusplus)
 #define inline __inline
 #endif
 /* ssize_t is also not available */
+#ifndef _SSIZE_T_DEFINED
+#define _SSIZE_T_DEFINED
 #include <basetsd.h>
 typedef SSIZE_T ssize_t;
+#endif /* _SSIZE_T_DEFINED */
 #endif /* _MSC_VER */
 
 #include <limits.h>
@@ -136,7 +142,7 @@
  * Internally, LIBUSB_API_VERSION is defined as follows:
  * (libusb major << 24) | (libusb minor << 16) | (16 bit incremental)
  */
-#define LIBUSB_API_VERSION 0x01000108
+#define LIBUSB_API_VERSION 0x01000109
 
 /* The following is kept for compatibility, but will be deprecated in the future */
 #define LIBUSBX_API_VERSION LIBUSB_API_VERSION
@@ -904,7 +910,7 @@
 
 /** \ingroup libusb_asyncio
  * Setup packet for control transfers. */
-#if defined(_MSC_VER)
+#if defined(_MSC_VER) || defined(__WATCOMC__)
 #pragma pack(push, 1)
 #endif
 struct libusb_control_setup {
@@ -932,7 +938,7 @@
 	/** Number of bytes to transfer */
 	uint16_t wLength;
 } LIBUSB_PACKED;
-#if defined(_MSC_VER)
+#if defined(_MSC_VER) || defined(__WATCOMC__)
 #pragma pack(pop)
 #endif
 
@@ -979,8 +985,9 @@
  * Sessions are created by libusb_init() 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. The default context
- * will be used.
+ * every function call where a context is required, and the default context
+ * will be used. Note that libusb_set_option(NULL, ...) is special, and adds
+ * an option to a list of default options for new contexts.
  *
  * For more information, see \ref libusb_contexts.
  */
@@ -989,7 +996,7 @@
 /** \ingroup libusb_dev
  * Structure representing a USB device detected on the system. This is an
  * opaque type for which you are only ever provided with a pointer, usually
- * originating from libusb_get_device_list().
+ * originating from libusb_get_device_list() or libusb_hotplug_register_callback().
  *
  * Certain operations can be performed on a device, but in order to do any
  * I/O you will have to first obtain a device handle using libusb_open().
@@ -1325,6 +1332,9 @@
 
 /** \ingroup libusb_lib
  *  Log callback mode.
+ *
+ *  Since version 1.0.23, \ref LIBUSB_API_VERSION >= 0x01000107
+ *
  * \see libusb_set_log_cb()
  */
 enum libusb_log_cb_mode {
@@ -1341,6 +1351,9 @@
  * is a global log message
  * \param level the log level, see \ref libusb_log_level for a description
  * \param str the log message
+ *
+ * Since version 1.0.23, \ref LIBUSB_API_VERSION >= 0x01000107
+ *
  * \see libusb_set_log_cb()
  */
 typedef void (LIBUSB_CALL *libusb_log_cb)(libusb_context *ctx,
@@ -2092,20 +2105,36 @@
 	 */
 	LIBUSB_OPTION_USE_USBDK = 1,
 
-	/** Set libusb has weak authority. With this option, libusb will skip
-	 * scan devices in libusb_init.
+	/** Do not scan for devices
 	 *
-	 * This option should be set before calling libusb_init(), otherwise
-	 * libusb_init will failed. Normally libusb_wrap_sys_device need set
-	 * this option.
+	 * With this option set, libusb will skip scanning devices in
+	 * libusb_init(). Must be set before calling libusb_init().
 	 *
-	 * Only valid on Linux-based operating system, such as Android.
+	 * Hotplug functionality will also be deactivated.
+	 *
+	 * The option is useful in combination with libusb_wrap_sys_device(),
+	 * which can access a device directly without prior device scanning.
+	 *
+	 * This is typically needed on Android, where access to USB devices
+	 * is limited.
+	 *
+	 * For LIBUSB_API_VERSION 0x01000108 it was called LIBUSB_OPTION_WEAK_AUTHORITY
+	 *
+	 * Only valid on Linux.
 	 */
-	LIBUSB_OPTION_WEAK_AUTHORITY = 2
+	LIBUSB_OPTION_NO_DEVICE_DISCOVERY = 2,
+
+#define LIBUSB_OPTION_WEAK_AUTHORITY LIBUSB_OPTION_NO_DEVICE_DISCOVERY
+
+	LIBUSB_OPTION_MAX = 3
 };
 
 int LIBUSB_CALL libusb_set_option(libusb_context *ctx, enum libusb_option option, ...);
 
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
 #if defined(__cplusplus)
 }
 #endif
diff --git a/libusb/libusbi.h b/libusb/libusbi.h
index 491114b..b1fc88c 100644
--- a/libusb/libusbi.h
+++ b/libusb/libusbi.h
@@ -83,6 +83,34 @@
 #define PTR_ALIGN(v) \
 	(((v) + (sizeof(void *) - 1)) & ~(sizeof(void *) - 1))
 
+/* Atomic operations
+ *
+ * Useful for reference counting or when accessing a value without a lock
+ *
+ * The following atomic operations are defined:
+ *   usbi_atomic_load() - Atomically read a variable's value
+ *   usbi_atomic_store() - Atomically write a new value value to a variable
+ *   usbi_atomic_inc() - Atomically increment a variable's value and return the new value
+ *   usbi_atomic_dec() - Atomically decrement a variable's value and return the new value
+ *
+ * All of these operations are ordered with each other, thus the effects of
+ * any one operation is guaranteed to be seen by any other operation.
+ */
+#ifdef _MSC_VER
+typedef volatile LONG usbi_atomic_t;
+#define usbi_atomic_load(a)	(*(a))
+#define usbi_atomic_store(a, v)	(*(a)) = (v)
+#define usbi_atomic_inc(a)	InterlockedIncrement((a))
+#define usbi_atomic_dec(a)	InterlockedDecrement((a))
+#else
+#include <stdatomic.h>
+typedef atomic_long usbi_atomic_t;
+#define usbi_atomic_load(a)	atomic_load((a))
+#define usbi_atomic_store(a, v)	atomic_store((a), (v))
+#define usbi_atomic_inc(a)	(atomic_fetch_add((a), 1) + 1)
+#define usbi_atomic_dec(a)	(atomic_fetch_add((a), -1) - 1)
+#endif
+
 /* Internal abstractions for event handling and thread synchronization */
 #if defined(PLATFORM_POSIX)
 #include "os/events_posix.h"
@@ -289,22 +317,23 @@
 #define usbi_err(ctx, ...)	_usbi_log(ctx, LIBUSB_LOG_LEVEL_ERROR, __VA_ARGS__)
 #define usbi_warn(ctx, ...)	_usbi_log(ctx, LIBUSB_LOG_LEVEL_WARNING, __VA_ARGS__)
 #define usbi_info(ctx, ...)	_usbi_log(ctx, LIBUSB_LOG_LEVEL_INFO, __VA_ARGS__)
-#define usbi_dbg(...)		_usbi_log(NULL, LIBUSB_LOG_LEVEL_DEBUG, __VA_ARGS__)
+#define usbi_dbg(ctx ,...)      	_usbi_log(ctx, LIBUSB_LOG_LEVEL_DEBUG, __VA_ARGS__)
 
 #else /* ENABLE_LOGGING */
 
 #define usbi_err(ctx, ...)	UNUSED(ctx)
 #define usbi_warn(ctx, ...)	UNUSED(ctx)
 #define usbi_info(ctx, ...)	UNUSED(ctx)
-#define usbi_dbg(...)		do {} while (0)
+#define usbi_dbg(ctx, ...)	do {} while (0)
 
 #endif /* ENABLE_LOGGING */
 
 #define DEVICE_CTX(dev)		((dev)->ctx)
-#define HANDLE_CTX(handle)	(DEVICE_CTX((handle)->dev))
-#define TRANSFER_CTX(transfer)	(HANDLE_CTX((transfer)->dev_handle))
+#define HANDLE_CTX(handle)	((handle) ? DEVICE_CTX((handle)->dev) : NULL)
 #define ITRANSFER_CTX(itransfer) \
-	(TRANSFER_CTX(USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer)))
+	((itransfer)->dev ? DEVICE_CTX((itransfer)->dev) : NULL)
+#define TRANSFER_CTX(transfer) \
+	(ITRANSFER_CTX(LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer)))
 
 #define IS_EPIN(ep)		(0 != ((ep) & LIBUSB_ENDPOINT_IN))
 #define IS_EPOUT(ep)		(!IS_EPIN(ep))
@@ -340,6 +369,9 @@
 	libusb_hotplug_callback_handle next_hotplug_cb_handle;
 	usbi_mutex_t hotplug_cbs_lock;
 
+	/* A flag to indicate that the context is ready for hotplug notifications */
+	usbi_atomic_t hotplug_ready;
+
 	/* this is a list of in-flight transfer handles, sorted by timeout
 	 * expiration. URBs to timeout the soonest are placed at the beginning of
 	 * the list, URBs that will time out later are placed after, and urbs with
@@ -404,13 +436,26 @@
 };
 
 extern struct libusb_context *usbi_default_context;
+extern struct libusb_context *usbi_fallback_context;
 
 extern struct list_head active_contexts_list;
 extern usbi_mutex_static_t active_contexts_lock;
 
 static inline struct libusb_context *usbi_get_context(struct libusb_context *ctx)
 {
-	return ctx ? ctx : usbi_default_context;
+	static int warned = 0;
+
+	if (!ctx) {
+		ctx = usbi_default_context;
+	}
+	if (!ctx) {
+		ctx = usbi_fallback_context;
+		if (ctx && warned == 0) {
+			usbi_err(ctx, "API misuse! Using non-default context as implicit default.");
+			warned = 1;
+		}
+	}
+	return ctx;
 }
 
 enum usbi_event_flags {
@@ -450,10 +495,7 @@
 }
 
 struct libusb_device {
-	/* lock protects refcnt, everything else is finalized at initialization
-	 * time */
-	usbi_mutex_t lock;
-	int refcnt;
+	usbi_atomic_t refcnt;
 
 	struct libusb_context *ctx;
 	struct libusb_device *parent_dev;
@@ -467,7 +509,7 @@
 	unsigned long session_data;
 
 	struct libusb_device_descriptor device_descriptor;
-	int attached;
+	usbi_atomic_t attached;
 };
 
 struct libusb_device_handle {
@@ -534,6 +576,10 @@
 	uint32_t state_flags;   /* Protected by usbi_transfer->lock */
 	uint32_t timeout_flags; /* Protected by the flying_stransfers_lock */
 
+	/* The device reference is held until destruction for logging
+	 * even after dev_handle is set to NULL.  */
+	struct libusb_device *dev;
+
 	/* this lock is held during libusb_submit_transfer() and
 	 * libusb_cancel_transfer() (allowing the OS backend to prevent duplicate
 	 * cancellation, submission-during-cancellation, etc). the OS backend
@@ -664,8 +710,75 @@
         uint16_t align;         /* Force 2-byte alignment */
 };
 
+enum usbi_hotplug_flags {
+	/* This callback is interested in device arrivals */
+	USBI_HOTPLUG_DEVICE_ARRIVED = LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED,
+
+	/* This callback is interested in device removals */
+	USBI_HOTPLUG_DEVICE_LEFT = LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
+
+	/* IMPORTANT: The values for the below entries must start *after*
+	 * the highest value of the above entries!!!
+	 */
+
+	/* The vendor_id field is valid for matching */
+	USBI_HOTPLUG_VENDOR_ID_VALID = (1U << 3),
+
+	/* The product_id field is valid for matching */
+	USBI_HOTPLUG_PRODUCT_ID_VALID = (1U << 4),
+
+	/* The dev_class field is valid for matching */
+	USBI_HOTPLUG_DEV_CLASS_VALID = (1U << 5),
+
+	/* This callback has been unregistered and needs to be freed */
+	USBI_HOTPLUG_NEEDS_FREE = (1U << 6),
+};
+
+struct usbi_hotplug_callback {
+	/* Flags that control how this callback behaves */
+	uint8_t flags;
+
+	/* Vendor ID to match (if flags says this is valid) */
+	uint16_t vendor_id;
+
+	/* Product ID to match (if flags says this is valid) */
+	uint16_t product_id;
+
+	/* Device class to match (if flags says this is valid) */
+	uint8_t dev_class;
+
+	/* Callback function to invoke for matching event/device */
+	libusb_hotplug_callback_fn cb;
+
+	/* Handle for this callback (used to match on deregister) */
+	libusb_hotplug_callback_handle handle;
+
+	/* User data that will be passed to the callback function */
+	void *user_data;
+
+	/* List this callback is registered in (ctx->hotplug_cbs) */
+	struct list_head list;
+};
+
+struct usbi_hotplug_message {
+	/* The hotplug event that occurred */
+	libusb_hotplug_event event;
+
+	/* The device for which this hotplug event occurred */
+	struct libusb_device *device;
+
+	/* List this message is contained in (ctx->hotplug_msgs) */
+	struct list_head list;
+};
+
 /* shared data and functions */
 
+void usbi_hotplug_init(struct libusb_context *ctx);
+void usbi_hotplug_exit(struct libusb_context *ctx);
+void usbi_hotplug_notification(struct libusb_context *ctx, struct libusb_device *dev,
+	libusb_hotplug_event event);
+void usbi_hotplug_process(struct libusb_context *ctx, struct list_head *hotplug_msgs);
+
 int usbi_io_init(struct libusb_context *ctx);
 void usbi_io_exit(struct libusb_context *ctx);
 
@@ -696,6 +809,13 @@
 	short poll_events);
 void usbi_remove_event_source(struct libusb_context *ctx, usbi_os_handle_t os_handle);
 
+struct usbi_option {
+  int is_set;
+  union {
+    int ival;
+  } arg;
+};
+
 /* OS event abstraction */
 
 int usbi_create_event(usbi_event_t *event);
@@ -793,7 +913,8 @@
 	 * data structures for later, etc.
 	 *
 	 * This function is called when a libusb user initializes the library
-	 * prior to use.
+	 * prior to use. Mutual exclusion with other init and exit calls is
+	 * guaranteed when this function is called.
 	 *
 	 * Return 0 on success, or a LIBUSB_ERROR code on failure.
 	 */
@@ -803,6 +924,8 @@
 	 * that was set up by init.
 	 *
 	 * This function is called when the user deinitializes the library.
+	 * Mutual exclusion with other init and exit calls is guaranteed when
+	 * this function is called.
 	 */
 	void (*exit)(struct libusb_context *ctx);
 
@@ -1365,6 +1488,12 @@
 #define for_each_removed_event_source_safe(ctx, e, n) \
 	for_each_safe_helper(e, n, &(ctx)->removed_event_sources, struct usbi_event_source)
 
+#define for_each_hotplug_cb(ctx, c) \
+	for_each_helper(c, &(ctx)->hotplug_cbs, struct usbi_hotplug_callback)
+
+#define for_each_hotplug_cb_safe(ctx, c, n) \
+	for_each_safe_helper(c, n, &(ctx)->hotplug_cbs, struct usbi_hotplug_callback)
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/libusb/os/darwin_usb.c b/libusb/os/darwin_usb.c
index e415589..388dbca 100644
--- a/libusb/os/darwin_usb.c
+++ b/libusb/os/darwin_usb.c
@@ -1,8 +1,8 @@
 /* -*- Mode: C; indent-tabs-mode:nil -*- */
 /*
  * darwin backend for libusb 1.0
- * Copyright © 2008-2020 Nathan Hjelm <hjelmn@cs.unm.edu>
- * Copyright © 2019-2020 Google LLC. All rights reserved.
+ * Copyright © 2008-2021 Nathan Hjelm <hjelmn@cs.unm.edu>
+ * Copyright © 2019-2021 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
@@ -41,6 +41,10 @@
  * function. Its use is also conditionalized to only older deployment targets. */
 #define OBJC_SILENCE_GC_DEPRECATIONS 1
 
+/* Default timeout to 10s for reenumerate. This is needed because USBDeviceReEnumerate
+ * does not return error status on macOS. */
+#define DARWIN_REENUMERATE_TIMEOUT_US (10 * USEC_PER_SEC)
+
 #include <AvailabilityMacros.h>
 #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 && MAC_OS_X_VERSION_MIN_REQUIRED < 101200
   #include <objc/objc-auto.h>
@@ -48,9 +52,11 @@
 
 #include "darwin_usb.h"
 
-static pthread_mutex_t libusb_darwin_init_mutex = PTHREAD_MUTEX_INITIALIZER;
 static int init_count = 0;
 
+/* Both kIOMasterPortDefault or kIOMainPortDefault are synonyms for 0. */
+static const mach_port_t darwin_default_master_port = 0;
+
 /* async event thread */
 static pthread_mutex_t libusb_darwin_at_mutex = PTHREAD_MUTEX_INITIALIZER;
 static pthread_cond_t  libusb_darwin_at_cond = PTHREAD_COND_INITIALIZER;
@@ -77,14 +83,17 @@
 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);
 static int darwin_release_interface(struct libusb_device_handle *dev_handle, uint8_t iface);
+static int darwin_reenumerate_device(struct libusb_device_handle *dev_handle, bool capture);
+static int darwin_clear_halt(struct libusb_device_handle *dev_handle, unsigned char endpoint);
 static int darwin_reset_device(struct libusb_device_handle *dev_handle);
+static int darwin_detach_kernel_driver (struct libusb_device_handle *dev_handle, uint8_t interface);
 static void darwin_async_io_callback (void *refcon, IOReturn result, void *arg0);
 
 static enum libusb_error darwin_scan_devices(struct libusb_context *ctx);
 static enum libusb_error process_new_device (struct libusb_context *ctx, struct darwin_cached_device *cached_device,
                                              UInt64 old_session_id);
 
-static enum libusb_error darwin_get_cached_device(io_service_t service, struct darwin_cached_device **cached_out,
+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);
 
 #if defined(ENABLE_LOGGING)
@@ -102,6 +111,9 @@
   case kIOReturnExclusiveAccess:
     return "another process has device opened for exclusive access";
   case kIOUSBPipeStalled:
+#if defined(kUSBHostReturnPipeStalled)
+  case kUSBHostReturnPipeStalled:
+#endif
     return "pipe is stalled";
   case kIOReturnError:
     return "could not establish a connection to the Darwin kernel";
@@ -141,16 +153,20 @@
   case kIOReturnExclusiveAccess:
     return LIBUSB_ERROR_ACCESS;
   case kIOUSBPipeStalled:
+#if defined(kUSBHostReturnPipeStalled)
+  case kUSBHostReturnPipeStalled:
+#endif
     return LIBUSB_ERROR_PIPE;
   case kIOReturnBadArgument:
     return LIBUSB_ERROR_INVALID_PARAM;
   case kIOUSBTransactionTimeout:
     return LIBUSB_ERROR_TIMEOUT;
+  case kIOUSBUnknownPipeErr:
+    return LIBUSB_ERROR_NOT_FOUND;
   case kIOReturnNotResponding:
   case kIOReturnAborted:
   case kIOReturnError:
   case kIOUSBNoAsyncPortErr:
-  case kIOUSBUnknownPipeErr:
   default:
     return LIBUSB_ERROR_OTHER;
   }
@@ -167,6 +183,7 @@
       (*(cached_dev->device))->Release(cached_dev->device);
       cached_dev->device = NULL;
     }
+    IOObjectRelease (cached_dev->service);
     free (cached_dev);
   }
 }
@@ -183,7 +200,9 @@
 
   uint8_t i, iface;
 
-  usbi_dbg ("converting ep address 0x%02x to pipeRef and interface", ep);
+  struct libusb_context *ctx = HANDLE_CTX(dev_handle);
+
+  usbi_dbg (ctx, "converting ep address 0x%02x to pipeRef and interface", ep);
 
   for (iface = 0 ; iface < USB_MAXINTERFACES ; iface++) {
     cInterface = &priv->interfaces[iface];
@@ -199,7 +218,7 @@
           if (interface_out)
             *interface_out = cInterface;
 
-          usbi_dbg ("pipe %d on interface %d matches", *pipep, iface);
+          usbi_dbg (ctx, "pipe %d on interface %d matches", *pipep, iface);
           return LIBUSB_SUCCESS;
         }
       }
@@ -240,7 +259,7 @@
       CFRelease (locationCF);
   }
 
-  return IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, deviceIterator);
+  return IOServiceGetMatchingServices(darwin_default_master_port, matchingDict, deviceIterator);
 }
 
 /* Returns 1 on success, 0 on failure. */
@@ -281,7 +300,7 @@
   return success;
 }
 
-static usb_device_t **darwin_device_from_service (io_service_t service)
+static usb_device_t **darwin_device_from_service (struct libusb_context *ctx, io_service_t service)
 {
   io_cf_plugin_ref_t *plugInInterface = NULL;
   usb_device_t **device;
@@ -300,14 +319,14 @@
       break;
     }
 
-    usbi_dbg ("set up plugin for service retry: %s", darwin_error_str (kresult));
+    usbi_dbg (ctx, "set up plugin for service retry: %s", darwin_error_str (kresult));
 
     /* sleep for a little while before trying again */
     nanosleep(&(struct timespec){.tv_sec = 0, .tv_nsec = 1000}, NULL);
   }
 
   if (kIOReturnSuccess != kresult || !plugInInterface) {
-    usbi_dbg ("could not set up plugin for service: %s", darwin_error_str (kresult));
+    usbi_dbg (ctx, "could not set up plugin for service: %s", darwin_error_str (kresult));
     return NULL;
   }
 
@@ -330,7 +349,7 @@
   usbi_mutex_lock(&active_contexts_lock);
 
   while ((service = IOIteratorNext(add_devices))) {
-    ret = darwin_get_cached_device (service, &cached_device, &old_session_id);
+    ret = darwin_get_cached_device (NULL, service, &cached_device, &old_session_id);
     if (ret < 0 || !cached_device->can_enumerate) {
       continue;
     }
@@ -341,7 +360,7 @@
     }
 
     if (cached_device->in_reenumerate) {
-      usbi_dbg ("cached device in reset state. reset complete...");
+      usbi_dbg (NULL, "cached device in reset state. reset complete...");
       cached_device->in_reenumerate = false;
     }
 
@@ -358,7 +377,7 @@
   struct darwin_cached_device *old_device;
 
   io_service_t device;
-  UInt64 session;
+  UInt64 session, locationID;
   int ret;
 
   usbi_mutex_lock(&active_contexts_lock);
@@ -368,6 +387,7 @@
 
     /* get the location from the i/o registry */
     ret = get_ioregistry_value_number (device, CFSTR("sessionID"), kCFNumberSInt64Type, &session);
+    (void) get_ioregistry_value_number (device, CFSTR("locationID"), kCFNumberSInt32Type, &locationID);
     IOObjectRelease (device);
     if (!ret)
       continue;
@@ -380,7 +400,8 @@
         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 ("detected device detached due to re-enumeration");
+          usbi_dbg (NULL, "detected device detached due to re-enumeration. sessionID: 0x%" PRIx64 ", locationID: 0x%" PRIx64,
+                    session, locationID);
 
           /* the device object is no longer usable so go ahead and release it */
           if (old_device->device) {
@@ -403,7 +424,7 @@
     }
 
     for_each_context(ctx) {
-      usbi_dbg ("notifying context %p of device disconnect", ctx);
+      usbi_dbg (ctx, "notifying context %p of device disconnect", ctx);
 
       dev = usbi_get_device_by_session_id(ctx, (unsigned long) session);
       if (dev) {
@@ -426,7 +447,7 @@
   /* since a kernel thread may notify the IOIterators used for
    * hotplug notification we can't just clear the iterators.
    * instead just wait until all IOService providers are quiet */
-  (void) IOKitWaitQuiet (kIOMasterPortDefault, &timeout);
+  (void) IOKitWaitQuiet (darwin_default_master_port, &timeout);
 }
 
 static void darwin_clear_iterator (io_iterator_t iter) {
@@ -473,7 +494,8 @@
   io_iterator_t          libusb_rem_device_iterator;
   io_iterator_t          libusb_add_device_iterator;
 
-  usbi_dbg ("creating hotplug event source");
+  /* ctx must only be used for logging during thread startup */
+  usbi_dbg (ctx, "creating hotplug event source");
 
   runloop = CFRunLoopGetCurrent ();
   CFRetain (runloop);
@@ -486,7 +508,7 @@
   CFRunLoopAddSource(runloop, libusb_shutdown_cfsource, kCFRunLoopDefaultMode);
 
   /* add the notification port to the run loop */
-  libusb_notification_port     = IONotificationPortCreate (kIOMasterPortDefault);
+  libusb_notification_port     = IONotificationPortCreate (darwin_default_master_port);
   libusb_notification_cfsource = IONotificationPortGetRunLoopSource (libusb_notification_port);
   CFRunLoopAddSource(runloop, libusb_notification_cfsource, kCFRunLoopDefaultMode);
 
@@ -494,7 +516,7 @@
   kresult = IOServiceAddMatchingNotification (libusb_notification_port, kIOTerminatedNotification,
                                               IOServiceMatching(darwin_device_class),
                                               darwin_devices_detached,
-                                              ctx, &libusb_rem_device_iterator);
+                                              NULL, &libusb_rem_device_iterator);
 
   if (kresult != kIOReturnSuccess) {
     usbi_err (ctx, "could not add hotplug event source: %s", darwin_error_str (kresult));
@@ -507,7 +529,7 @@
   kresult = IOServiceAddMatchingNotification(libusb_notification_port, kIOFirstMatchNotification,
                                               IOServiceMatching(darwin_device_class),
                                               darwin_devices_attached,
-                                              ctx, &libusb_add_device_iterator);
+                                              NULL, &libusb_add_device_iterator);
 
   if (kresult != kIOReturnSuccess) {
     usbi_err (ctx, "could not add hotplug event source: %s", darwin_error_str (kresult));
@@ -520,7 +542,7 @@
   darwin_clear_iterator (libusb_rem_device_iterator);
   darwin_clear_iterator (libusb_add_device_iterator);
 
-  usbi_dbg ("darwin event thread ready to receive events");
+  usbi_dbg (ctx, "darwin event thread ready to receive events");
 
   /* signal the main thread that the hotplug runloop has been created. */
   pthread_mutex_lock (&libusb_darwin_at_mutex);
@@ -532,7 +554,7 @@
   /* run the runloop */
   CFRunLoopRun();
 
-  usbi_dbg ("darwin event thread exiting");
+  usbi_dbg (NULL, "darwin event thread exiting");
 
   /* signal the main thread that the hotplug runloop has finished. */
   pthread_mutex_lock (&libusb_darwin_at_mutex);
@@ -567,23 +589,20 @@
   list_for_each_entry_safe(dev, next, &darwin_cached_devices, list, struct darwin_cached_device) {
     darwin_deref_cached_device(dev);
   }
-
-  darwin_cached_devices.prev = darwin_cached_devices.next = NULL;
 }
 
 static int darwin_init(struct libusb_context *ctx) {
   bool first_init;
   int rc;
 
-  pthread_mutex_lock (&libusb_darwin_init_mutex);
-
   first_init = (1 == ++init_count);
 
   do {
     if (first_init) {
-      assert (NULL == darwin_cached_devices.next);
-      list_init (&darwin_cached_devices);
-
+      if (NULL == darwin_cached_devices.next) {
+        list_init (&darwin_cached_devices);
+      }
+      assert(list_empty(&darwin_cached_devices));
 #if !defined(HAVE_CLOCK_GETTIME)
       /* create the clocks that will be used if clock_gettime() is not available */
       host_name_port_t host_self;
@@ -632,16 +651,12 @@
     --init_count;
   }
 
-  pthread_mutex_unlock (&libusb_darwin_init_mutex);
-
   return rc;
 }
 
 static void darwin_exit (struct libusb_context *ctx) {
   UNUSED(ctx);
 
-  pthread_mutex_lock (&libusb_darwin_init_mutex);
-
   if (0 == --init_count) {
     /* stop the event runloop and wait for the thread to terminate. */
     pthread_mutex_lock (&libusb_darwin_at_mutex);
@@ -659,8 +674,6 @@
     mach_port_deallocate(mach_task_self(), clock_monotonic);
 #endif
   }
-
-  pthread_mutex_unlock (&libusb_darwin_init_mutex);
 }
 
 static int get_configuration_index (struct libusb_device *dev, UInt8 config_value) {
@@ -744,7 +757,7 @@
      not usable anyway */
   if (0x05ac == libusb_le16_to_cpu (dev->dev_descriptor.idVendor) &&
       0x8005 == libusb_le16_to_cpu (dev->dev_descriptor.idProduct)) {
-    usbi_dbg ("ignoring configuration on root hub simulation");
+    usbi_dbg (ctx, "ignoring configuration on root hub simulation");
     dev->active_config = 0;
     return LIBUSB_SUCCESS;
   }
@@ -787,7 +800,7 @@
     /* not configured */
     dev->active_config = 0;
 
-  usbi_dbg ("active config: %u, first config: %u", dev->active_config, dev->first_config);
+  usbi_dbg (ctx, "active config: %u, first config: %u", dev->active_config, dev->first_config);
 
   return LIBUSB_SUCCESS;
 }
@@ -812,7 +825,7 @@
   return (*device)->DeviceRequestTO (device, &req);
 }
 
-static enum libusb_error darwin_cache_device_descriptor (struct darwin_cached_device *dev) {
+static enum libusb_error darwin_cache_device_descriptor (struct libusb_context *ctx, struct darwin_cached_device *dev) {
   usb_device_t **device = dev->device;
   int retries = 1;
   long delay = 30000; // microseconds
@@ -850,7 +863,7 @@
                                     0 == dev->dev_descriptor.bcdUSB)) {
       /* work around for incorrectly configured devices */
       if (try_reconfigure && is_open) {
-        usbi_dbg("descriptor appears to be invalid. resetting configuration before trying again...");
+        usbi_dbg(ctx, "descriptor appears to be invalid. resetting configuration before trying again...");
 
         /* set the first configuration */
         (*device)->SetConfiguration(device, 1);
@@ -881,7 +894,7 @@
         if (kIOReturnSuccess != ret2) {
           /* prevent log spew from poorly behaving devices.  this indicates the
              os actually had trouble communicating with the device */
-          usbi_dbg("could not retrieve device descriptor. failed to unsuspend: %s",darwin_error_str(ret2));
+          usbi_dbg(ctx, "could not retrieve device descriptor. failed to unsuspend: %s",darwin_error_str(ret2));
         } else
           unsuspended = 1;
 
@@ -890,7 +903,7 @@
     }
 
     if (kIOReturnSuccess != ret) {
-      usbi_dbg("kernel responded with code: 0x%08x. sleeping for %ld ms before trying again", ret, delay/1000);
+      usbi_dbg(ctx, "kernel responded with code: 0x%08x. sleeping for %ld ms before trying again", ret, delay/1000);
       /* sleep for a little while before trying again */
       nanosleep(&(struct timespec){delay / 1000000, (delay * 1000) % 1000000000}, NULL);
     }
@@ -906,10 +919,10 @@
   if (ret != kIOReturnSuccess) {
     /* a debug message was already printed out for this error */
     if (LIBUSB_CLASS_HUB == bDeviceClass)
-      usbi_dbg ("could not retrieve device descriptor %.4x:%.4x: %s (%x). skipping device",
+      usbi_dbg (ctx, "could not retrieve device descriptor %.4x:%.4x: %s (%x). skipping device",
                 idVendor, idProduct, darwin_error_str (ret), ret);
     else
-      usbi_warn (NULL, "could not retrieve device descriptor %.4x:%.4x: %s (%x). skipping device",
+      usbi_warn (ctx, "could not retrieve device descriptor %.4x:%.4x: %s (%x). skipping device",
                  idVendor, idProduct, darwin_error_str (ret), ret);
     return darwin_to_libusb (ret);
   }
@@ -922,20 +935,20 @@
     return LIBUSB_ERROR_NO_DEVICE;
   }
 
-  usbi_dbg ("cached device descriptor:");
-  usbi_dbg ("  bDescriptorType:    0x%02x", dev->dev_descriptor.bDescriptorType);
-  usbi_dbg ("  bcdUSB:             0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.bcdUSB));
-  usbi_dbg ("  bDeviceClass:       0x%02x", dev->dev_descriptor.bDeviceClass);
-  usbi_dbg ("  bDeviceSubClass:    0x%02x", dev->dev_descriptor.bDeviceSubClass);
-  usbi_dbg ("  bDeviceProtocol:    0x%02x", dev->dev_descriptor.bDeviceProtocol);
-  usbi_dbg ("  bMaxPacketSize0:    0x%02x", dev->dev_descriptor.bMaxPacketSize0);
-  usbi_dbg ("  idVendor:           0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.idVendor));
-  usbi_dbg ("  idProduct:          0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.idProduct));
-  usbi_dbg ("  bcdDevice:          0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.bcdDevice));
-  usbi_dbg ("  iManufacturer:      0x%02x", dev->dev_descriptor.iManufacturer);
-  usbi_dbg ("  iProduct:           0x%02x", dev->dev_descriptor.iProduct);
-  usbi_dbg ("  iSerialNumber:      0x%02x", dev->dev_descriptor.iSerialNumber);
-  usbi_dbg ("  bNumConfigurations: 0x%02x", dev->dev_descriptor.bNumConfigurations);
+  usbi_dbg (ctx, "cached device descriptor:");
+  usbi_dbg (ctx, "  bDescriptorType:    0x%02x", dev->dev_descriptor.bDescriptorType);
+  usbi_dbg (ctx, "  bcdUSB:             0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.bcdUSB));
+  usbi_dbg (ctx, "  bDeviceClass:       0x%02x", dev->dev_descriptor.bDeviceClass);
+  usbi_dbg (ctx, "  bDeviceSubClass:    0x%02x", dev->dev_descriptor.bDeviceSubClass);
+  usbi_dbg (ctx, "  bDeviceProtocol:    0x%02x", dev->dev_descriptor.bDeviceProtocol);
+  usbi_dbg (ctx, "  bMaxPacketSize0:    0x%02x", dev->dev_descriptor.bMaxPacketSize0);
+  usbi_dbg (ctx, "  idVendor:           0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.idVendor));
+  usbi_dbg (ctx, "  idProduct:          0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.idProduct));
+  usbi_dbg (ctx, "  bcdDevice:          0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.bcdDevice));
+  usbi_dbg (ctx, "  iManufacturer:      0x%02x", dev->dev_descriptor.iManufacturer);
+  usbi_dbg (ctx, "  iProduct:           0x%02x", dev->dev_descriptor.iProduct);
+  usbi_dbg (ctx, "  iSerialNumber:      0x%02x", dev->dev_descriptor.iSerialNumber);
+  usbi_dbg (ctx, "  bNumConfigurations: 0x%02x", dev->dev_descriptor.bNumConfigurations);
 
   dev->can_enumerate = 1;
 
@@ -979,7 +992,7 @@
   return false;
 }
 
-static enum libusb_error darwin_get_cached_device(io_service_t service, struct darwin_cached_device **cached_out,
+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_cached_device *new_device;
   UInt64 sessionID = 0, parent_sessionID = 0;
@@ -996,28 +1009,28 @@
   (void) get_ioregistry_value_number (service, CFSTR("sessionID"), kCFNumberSInt64Type, &sessionID);
   (void) get_ioregistry_value_number (service, CFSTR("locationID"), kCFNumberSInt32Type, &locationID);
   if (!get_device_port (service, &port)) {
-    usbi_dbg("could not get connected port number");
+    usbi_dbg(ctx, "could not get connected port number");
   }
 
-  usbi_dbg("finding cached device for sessionID 0x%" PRIx64, sessionID);
+  usbi_dbg(ctx, "finding cached device for sessionID 0x%" PRIx64, sessionID);
 
   if (get_device_parent_sessionID(service, &parent_sessionID)) {
-    usbi_dbg("parent sessionID: 0x%" PRIx64, parent_sessionID);
+    usbi_dbg(ctx, "parent sessionID: 0x%" PRIx64, parent_sessionID);
   }
 
   usbi_mutex_lock(&darwin_cached_devices_lock);
   do {
     list_for_each_entry(new_device, &darwin_cached_devices, list, struct darwin_cached_device) {
-      usbi_dbg("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%x against cached device with sessionID/locationID 0x%" PRIx64 "/0x%x",
                sessionID, locationID, new_device->session, new_device->location);
       if (new_device->location == locationID && new_device->in_reenumerate) {
-        usbi_dbg ("found cached device with matching location that is being re-enumerated");
+        usbi_dbg (ctx, "found cached device with matching location that is being re-enumerated");
         *old_session_id = new_device->session;
         break;
       }
 
       if (new_device->session == sessionID) {
-        usbi_dbg("using cached device for device");
+        usbi_dbg(ctx, "using cached device for device");
         *cached_out = new_device;
         break;
       }
@@ -1026,9 +1039,9 @@
     if (*cached_out)
       break;
 
-    usbi_dbg("caching new device with sessionID 0x%" PRIx64, sessionID);
+    usbi_dbg(ctx, "caching new device with sessionID 0x%" PRIx64, sessionID);
 
-    device = darwin_device_from_service (service);
+    device = darwin_device_from_service (ctx, service);
     if (!device) {
       ret = LIBUSB_ERROR_NO_DEVICE;
       break;
@@ -1052,6 +1065,9 @@
       (*device)->GetLocationID (device, &new_device->location);
       new_device->port = port;
       new_device->parent_session = parent_sessionID;
+    } else {
+      /* release the ref to old device's service */
+      IOObjectRelease (new_device->service);
     }
 
     /* keep track of devices regardless of if we successfully enumerate them to
@@ -1060,9 +1076,13 @@
 
     new_device->session = sessionID;
     new_device->device = device;
+    new_device->service = service;
+
+    /* retain the service */
+    IOObjectRetain (service);
 
     /* cache the device descriptor */
-    ret = darwin_cache_device_descriptor(new_device);
+    ret = darwin_cache_device_descriptor(ctx, new_device);
     if (ret)
       break;
 
@@ -1094,14 +1114,14 @@
       break;
 
     if (0 != old_session_id) {
-      usbi_dbg ("re-using existing device from context %p for with session 0x%" PRIx64 " new session 0x%" PRIx64,
+      usbi_dbg (ctx, "re-using existing device from context %p for with session 0x%" PRIx64 " new session 0x%" PRIx64,
                 ctx, old_session_id, cached_device->session);
       /* save the libusb device before the session id is updated */
       dev = usbi_get_device_by_session_id (ctx, (unsigned long) old_session_id);
     }
 
     if (!dev) {
-      usbi_dbg ("allocating new device in context %p for with session 0x%" PRIx64,
+      usbi_dbg (ctx, "allocating new device in context %p for with session 0x%" PRIx64,
                 ctx, cached_device->session);
 
       dev = usbi_alloc_device(ctx, (unsigned long) cached_device->session);
@@ -1114,6 +1134,8 @@
       priv->dev = cached_device;
       darwin_ref_cached_device (priv->dev);
       dev->port_number    = cached_device->port;
+      /* the location ID encodes the path to the device. the top byte of the location ID contains the bus number
+         (numbered from 0). the remaining bytes can be used to construct the device tree for that bus. */
       dev->bus_number     = cached_device->location >> 24;
       assert(cached_device->address <= UINT8_MAX);
       dev->device_address = (uint8_t)cached_device->address;
@@ -1127,10 +1149,13 @@
     usbi_localize_device_descriptor(&dev->device_descriptor);
     dev->session_data = cached_device->session;
 
+    if (NULL != dev->parent_dev) {
+      libusb_unref_device(dev->parent_dev);
+      dev->parent_dev = NULL;
+    }
+
     if (cached_device->parent_session > 0) {
       dev->parent_dev = usbi_get_device_by_session_id (ctx, (unsigned long) cached_device->parent_session);
-    } else {
-      dev->parent_dev = NULL;
     }
 
     (*(priv->dev->device))->GetDeviceSpeed (priv->dev->device, &devSpeed);
@@ -1139,7 +1164,7 @@
     case kUSBDeviceSpeedLow: dev->speed = LIBUSB_SPEED_LOW; break;
     case kUSBDeviceSpeedFull: dev->speed = LIBUSB_SPEED_FULL; break;
     case kUSBDeviceSpeedHigh: dev->speed = LIBUSB_SPEED_HIGH; break;
-#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1080
     case kUSBDeviceSpeedSuper: dev->speed = LIBUSB_SPEED_SUPER; break;
 #endif
 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200
@@ -1153,7 +1178,7 @@
     if (ret < 0)
       break;
 
-    usbi_dbg ("found device with address %d port = %d parent = %p at %p", dev->device_address,
+    usbi_dbg (ctx, "found device with address %d port = %d parent = %p at %p", dev->device_address,
               dev->port_number, (void *) dev->parent_dev, priv->dev->sys_path);
 
   } while (0);
@@ -1180,7 +1205,7 @@
     return darwin_to_libusb (kresult);
 
   while ((service = IOIteratorNext (deviceIterator))) {
-    ret = darwin_get_cached_device (service, &cached_device, &old_session_id);
+    ret = darwin_get_cached_device (ctx, service, &cached_device, &old_session_id);
     if (ret < 0 || !cached_device->can_enumerate) {
       continue;
     }
@@ -1232,14 +1257,14 @@
 
     CFRetain (libusb_darwin_acfl);
 
-    /* add the cfSource to the aync run loop */
+    /* add the cfSource to the async run loop */
     CFRunLoopAddSource(libusb_darwin_acfl, priv->cfSource, kCFRunLoopCommonModes);
   }
 
   /* device opened successfully */
   dpriv->open_count++;
 
-  usbi_dbg ("device open for access");
+  usbi_dbg (HANDLE_CTX(dev_handle), "device open for access");
 
   return 0;
 }
@@ -1257,6 +1282,10 @@
   }
 
   dpriv->open_count--;
+  if (NULL == dpriv->device) {
+    usbi_warn (HANDLE_CTX (dev_handle), "darwin_close device missing IOService");
+    return;
+  }
 
   /* make sure all interfaces are released */
   for (i = 0 ; i < USB_MAXINTERFACES ; i++)
@@ -1362,28 +1391,39 @@
 
   /* 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 dont_care1, dont_care3;
-  UInt16 dont_care2;
   int rc;
+  struct libusb_context *ctx = HANDLE_CTX (dev_handle);
 
-  usbi_dbg ("building table of endpoints.");
+
+  usbi_dbg (ctx, "building table of endpoints.");
 
   /* retrieve the total number of endpoints on this interface */
   kresult = (*(cInterface->interface))->GetNumEndpoints(cInterface->interface, &numep);
   if (kresult != kIOReturnSuccess) {
-    usbi_err (HANDLE_CTX (dev_handle), "can't get number of endpoints for interface: %s", darwin_error_str(kresult));
+    usbi_err (ctx, "can't get number of endpoints for interface: %s", darwin_error_str(kresult));
     return darwin_to_libusb (kresult);
   }
 
   /* 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
     if (kresult != kIOReturnSuccess) {
       /* probably a buggy device. try to get the endpoint address from the descriptors */
       struct libusb_config_descriptor *config;
@@ -1401,6 +1441,10 @@
         return rc;
       }
 
+      if (iface >= config->bNumInterfaces) {
+        usbi_err (HANDLE_CTX (dev_handle), "interface %d out of range for device", iface);
+        return LIBUSB_ERROR_NOT_FOUND;
+      }
       endpoint_desc = config->interface[iface].altsetting[alt_setting].endpoint + i - 1;
 
       cInterface->endpoint_addrs[i - 1] = endpoint_desc->bEndpointAddress;
@@ -1408,7 +1452,7 @@
       cInterface->endpoint_addrs[i - 1] = (UInt8)(((kUSBIn == direction) << kUSBRqDirnShift) | (number & LIBUSB_ENDPOINT_ADDRESS_MASK));
     }
 
-    usbi_dbg ("interface: %i pipe %i: dir: %i number: %i", iface, i, cInterface->endpoint_addrs[i - 1] >> kUSBRqDirnShift,
+    usbi_dbg (ctx, "interface: %i pipe %i: dir: %i number: %i", iface, i, cInterface->endpoint_addrs[i - 1] >> kUSBRqDirnShift,
               cInterface->endpoint_addrs[i - 1] & LIBUSB_ENDPOINT_ADDRESS_MASK);
   }
 
@@ -1429,30 +1473,32 @@
   /* current interface */
   struct darwin_interface *cInterface = &priv->interfaces[iface];
 
+  struct libusb_context *ctx = HANDLE_CTX (dev_handle);
+
   kresult = darwin_get_interface (dpriv->device, iface, &usbInterface);
   if (kresult != kIOReturnSuccess)
     return darwin_to_libusb (kresult);
 
   /* make sure we have an interface */
   if (!usbInterface && dpriv->first_config != 0) {
-    usbi_info (HANDLE_CTX (dev_handle), "no interface found; setting configuration: %d", dpriv->first_config);
+    usbi_info (ctx, "no interface found; setting configuration: %d", dpriv->first_config);
 
     /* set the configuration */
     ret = darwin_set_configuration (dev_handle, (int) dpriv->first_config);
     if (ret != LIBUSB_SUCCESS) {
-      usbi_err (HANDLE_CTX (dev_handle), "could not set configuration");
+      usbi_err (ctx, "could not set configuration");
       return ret;
     }
 
     kresult = darwin_get_interface (dpriv->device, iface, &usbInterface);
     if (kresult != kIOReturnSuccess) {
-      usbi_err (HANDLE_CTX (dev_handle), "darwin_get_interface: %s", darwin_error_str(kresult));
+      usbi_err (ctx, "darwin_get_interface: %s", darwin_error_str(kresult));
       return darwin_to_libusb (kresult);
     }
   }
 
   if (!usbInterface) {
-    usbi_err (HANDLE_CTX (dev_handle), "interface not found");
+    usbi_info (ctx, "interface not found");
     return LIBUSB_ERROR_NOT_FOUND;
   }
 
@@ -1464,12 +1510,12 @@
   (void)IOObjectRelease (usbInterface);
 
   if (kresult != kIOReturnSuccess) {
-    usbi_err (HANDLE_CTX (dev_handle), "IOCreatePlugInInterfaceForService: %s", darwin_error_str(kresult));
+    usbi_err (ctx, "IOCreatePlugInInterfaceForService: %s", darwin_error_str(kresult));
     return darwin_to_libusb (kresult);
   }
 
   if (!plugInInterface) {
-    usbi_err (HANDLE_CTX (dev_handle), "plugin interface not found");
+    usbi_err (ctx, "plugin interface not found");
     return LIBUSB_ERROR_NOT_FOUND;
   }
 
@@ -1481,14 +1527,14 @@
   /* Use release instead of IODestroyPlugInInterface to avoid stopping IOServices associated with this device */
   (*plugInInterface)->Release (plugInInterface);
   if (kresult != kIOReturnSuccess || !cInterface->interface) {
-    usbi_err (HANDLE_CTX (dev_handle), "QueryInterface: %s", darwin_error_str(kresult));
+    usbi_err (ctx, "QueryInterface: %s", darwin_error_str(kresult));
     return darwin_to_libusb (kresult);
   }
 
   /* claim the interface */
   kresult = (*(cInterface->interface))->USBInterfaceOpen(cInterface->interface);
   if (kresult != kIOReturnSuccess) {
-    usbi_err (HANDLE_CTX (dev_handle), "USBInterfaceOpen: %s", darwin_error_str(kresult));
+    usbi_info (ctx, "USBInterfaceOpen: %s", darwin_error_str(kresult));
     return darwin_to_libusb (kresult);
   }
 
@@ -1497,7 +1543,7 @@
   if (ret) {
     /* this should not happen */
     darwin_release_interface (dev_handle, iface);
-    usbi_err (HANDLE_CTX (dev_handle), "could not build endpoint table");
+    usbi_err (ctx, "could not build endpoint table");
     return ret;
   }
 
@@ -1506,7 +1552,7 @@
   /* create async event source */
   kresult = (*(cInterface->interface))->CreateInterfaceAsyncEventSource (cInterface->interface, &cInterface->cfSource);
   if (kresult != kIOReturnSuccess) {
-    usbi_err (HANDLE_CTX (dev_handle), "could not create async event source");
+    usbi_err (ctx, "could not create async event source");
 
     /* can't continue without an async event source */
     (void)darwin_release_interface (dev_handle, iface);
@@ -1517,7 +1563,7 @@
   /* add the cfSource to the async thread's run loop */
   CFRunLoopAddSource(libusb_darwin_acfl, cInterface->cfSource, kCFRunLoopDefaultMode);
 
-  usbi_dbg ("interface opened");
+  usbi_dbg (ctx, "interface opened");
 
   return LIBUSB_SUCCESS;
 }
@@ -1540,6 +1586,7 @@
   if (cInterface->cfSource) {
     CFRunLoopRemoveSource (libusb_darwin_acfl, cInterface->cfSource, kCFRunLoopDefaultMode);
     CFRelease (cInterface->cfSource);
+    cInterface->cfSource = NULL;
   }
 
   kresult = (*(cInterface->interface))->USBInterfaceClose(cInterface->interface);
@@ -1555,6 +1602,30 @@
   return darwin_to_libusb (kresult);
 }
 
+static int check_alt_setting_and_clear_halt(struct libusb_device_handle *dev_handle, uint8_t altsetting, struct darwin_interface *cInterface) {
+  enum libusb_error ret;
+  IOReturn kresult;
+  uint8_t current_alt_setting;
+
+  kresult = (*(cInterface->interface))->GetAlternateSetting (cInterface->interface, &current_alt_setting);
+  if (kresult == kIOReturnSuccess && altsetting != current_alt_setting) {
+    return LIBUSB_ERROR_PIPE;
+  }
+
+  for (int i = 0 ; i < cInterface->num_endpoints ; i++) {
+    ret = darwin_clear_halt(dev_handle, cInterface->endpoint_addrs[i]);
+    if (LIBUSB_SUCCESS != ret) {
+      usbi_warn(HANDLE_CTX (dev_handle), "error clearing pipe halt for endpoint %d", i);
+      if (LIBUSB_ERROR_NOT_FOUND == ret) {
+        /* may need to re-open the interface */
+        return ret;
+      }
+    }
+  }
+
+  return LIBUSB_SUCCESS;
+}
+
 static int darwin_set_interface_altsetting(struct libusb_device_handle *dev_handle, uint8_t iface, uint8_t altsetting) {
   struct darwin_device_handle_priv *priv = usbi_get_device_handle_priv(dev_handle);
   IOReturn kresult;
@@ -1567,19 +1638,41 @@
     return LIBUSB_ERROR_NO_DEVICE;
 
   kresult = (*(cInterface->interface))->SetAlternateInterface (cInterface->interface, altsetting);
-  if (kresult != kIOReturnSuccess)
-    darwin_reset_device (dev_handle);
-
-  /* update list of endpoints */
-  ret = get_endpoints (dev_handle, iface);
-  if (ret) {
-    /* this should not happen */
-    darwin_release_interface (dev_handle, iface);
-    usbi_err (HANDLE_CTX (dev_handle), "could not build endpoint table");
+  if (kresult == kIOReturnSuccess) {
+    /* update the list of endpoints */
+    ret = get_endpoints (dev_handle, iface);
+    if (ret) {
+      /* this should not happen */
+      darwin_release_interface (dev_handle, iface);
+      usbi_err (HANDLE_CTX (dev_handle), "could not build endpoint table");
+    }
     return ret;
   }
 
-  return darwin_to_libusb (kresult);
+  usbi_warn (HANDLE_CTX (dev_handle), "SetAlternateInterface: %s", darwin_error_str(kresult));
+
+  ret = darwin_to_libusb(kresult);
+  if (ret != LIBUSB_ERROR_PIPE) {
+    return ret;
+  }
+
+  /* If a device only supports a default setting for the specified interface, then a STALL
+     (kIOUSBPipeStalled) may be returned. Ref: USB 2.0 specs 9.4.10.
+     Mimic the behaviour in e.g. the Linux kernel: in such case, reset all endpoints
+     of the interface (as would have been done per 9.1.1.5) and return success. */
+
+  ret = check_alt_setting_and_clear_halt(dev_handle, altsetting, cInterface);
+  if (LIBUSB_ERROR_NOT_FOUND == ret) {
+    /* For some reason we need to reclaim the interface after the pipe error with some versions of macOS */
+    ret = darwin_claim_interface (dev_handle, iface);
+    if (LIBUSB_SUCCESS != ret) {
+      darwin_release_interface (dev_handle, iface);
+      usbi_err (HANDLE_CTX (dev_handle), "could not reclaim interface: %s", darwin_error_str(kresult));
+    }
+    ret = check_alt_setting_and_clear_halt(dev_handle, altsetting, cInterface);
+  }
+
+  return ret;
 }
 
 static int darwin_clear_halt(struct libusb_device_handle *dev_handle, unsigned char endpoint) {
@@ -1610,6 +1703,8 @@
   int open_count = dpriv->open_count;
   int ret;
 
+  struct libusb_context *ctx = HANDLE_CTX (dev_handle);
+
   /* clear claimed interfaces temporarily */
   dev_handle->claimed_interfaces = 0;
 
@@ -1629,16 +1724,16 @@
   }
 
   if (dpriv->active_config != active_config) {
-    usbi_dbg ("darwin/restore_state: restoring configuration %d...", active_config);
+    usbi_dbg (ctx, "darwin/restore_state: restoring configuration %d...", active_config);
 
     ret = darwin_set_configuration (dev_handle, active_config);
     if (LIBUSB_SUCCESS != ret) {
-      usbi_dbg ("darwin/restore_state: could not restore configuration");
+      usbi_dbg (ctx, "darwin/restore_state: could not restore configuration");
       return LIBUSB_ERROR_NOT_FOUND;
     }
   }
 
-  usbi_dbg ("darwin/restore_state: reclaiming interfaces");
+  usbi_dbg (ctx, "darwin/restore_state: reclaiming interfaces");
 
   if (claimed_interfaces) {
     for (uint8_t iface = 0 ; iface < USB_MAXINTERFACES ; ++iface) {
@@ -1646,11 +1741,11 @@
         continue;
       }
 
-      usbi_dbg ("darwin/restore_state: re-claiming interface %u", iface);
+      usbi_dbg (ctx, "darwin/restore_state: re-claiming interface %u", iface);
 
       ret = darwin_claim_interface (dev_handle, iface);
       if (LIBUSB_SUCCESS != ret) {
-        usbi_dbg ("darwin/restore_state: could not claim interface %u", iface);
+        usbi_dbg (ctx, "darwin/restore_state: could not claim interface %u", iface);
         return LIBUSB_ERROR_NOT_FOUND;
       }
 
@@ -1658,21 +1753,24 @@
     }
   }
 
-  usbi_dbg ("darwin/restore_state: device state restored");
+  usbi_dbg (ctx, "darwin/restore_state: device state restored");
 
   return LIBUSB_SUCCESS;
 }
 
-static int darwin_reset_device(struct libusb_device_handle *dev_handle) {
+static int darwin_reenumerate_device (struct libusb_device_handle *dev_handle, bool capture) {
   struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev);
   unsigned long claimed_interfaces = dev_handle->claimed_interfaces;
   int8_t active_config = dpriv->active_config;
+  UInt32 options = 0;
   IOUSBDeviceDescriptor descriptor;
   IOUSBConfigurationDescriptorPtr cached_configuration;
   IOUSBConfigurationDescriptor *cached_configurations;
   IOReturn kresult;
   UInt8 i;
 
+  struct libusb_context *ctx = HANDLE_CTX (dev_handle);
+
   if (dpriv->in_reenumerate) {
     /* ack, two (or more) threads are trying to reset the device! abort! */
     return LIBUSB_ERROR_NOT_FOUND;
@@ -1689,62 +1787,152 @@
     memcpy (cached_configurations + i, cached_configuration, sizeof (cached_configurations[i]));
   }
 
+  /* if we need to release capture */
+  if (HAS_CAPTURE_DEVICE()) {
+    if (capture) {
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000
+      options |= kUSBReEnumerateCaptureDeviceMask;
+#endif
+    }
+  } else {
+    capture = false;
+  }
+
   /* from macOS 10.11 ResetDevice no longer does anything so just use USBDeviceReEnumerate */
-  kresult = (*(dpriv->device))->USBDeviceReEnumerate (dpriv->device, 0);
+  kresult = (*(dpriv->device))->USBDeviceReEnumerate (dpriv->device, options);
   if (kresult != kIOReturnSuccess) {
-    usbi_err (HANDLE_CTX (dev_handle), "USBDeviceReEnumerate: %s", darwin_error_str (kresult));
+    usbi_err (ctx, "USBDeviceReEnumerate: %s", darwin_error_str (kresult));
     dpriv->in_reenumerate = false;
     return darwin_to_libusb (kresult);
   }
 
-  usbi_dbg ("darwin/reset_device: waiting for re-enumeration to complete...");
+  /* capture mode does not re-enumerate but it does require re-open */
+  if (capture) {
+    usbi_dbg (ctx, "darwin/reenumerate_device: restoring state...");
+    dpriv->in_reenumerate = false;
+    return darwin_restore_state (dev_handle, active_config, claimed_interfaces);
+  }
+
+  usbi_dbg (ctx, "darwin/reenumerate_device: waiting for re-enumeration to complete...");
+
+  struct timespec start;
+  usbi_get_monotonic_time(&start);
 
   while (dpriv->in_reenumerate) {
     struct timespec delay = {.tv_sec = 0, .tv_nsec = 1000};
     nanosleep (&delay, NULL);
+
+    struct timespec now;
+    usbi_get_monotonic_time(&now);
+    unsigned long elapsed_us = (now.tv_sec - start.tv_sec) * USEC_PER_SEC +
+                                (now.tv_nsec - start.tv_nsec) / 1000;
+
+    if (elapsed_us >= DARWIN_REENUMERATE_TIMEOUT_US) {
+      usbi_err (ctx, "darwin/reenumerate_device: timeout waiting for reenumerate");
+      dpriv->in_reenumerate = false;
+      return LIBUSB_ERROR_TIMEOUT;
+    }
   }
 
   /* compare descriptors */
-  usbi_dbg ("darwin/reset_device: checking whether descriptors changed");
+  usbi_dbg (ctx, "darwin/reenumerate_device: checking whether descriptors changed");
 
   if (memcmp (&descriptor, &dpriv->dev_descriptor, sizeof (descriptor))) {
     /* device descriptor changed. need to return not found. */
-    usbi_dbg ("darwin/reset_device: device descriptor changed");
+    usbi_dbg (ctx, "darwin/reenumerate_device: device descriptor changed");
     return LIBUSB_ERROR_NOT_FOUND;
   }
 
   for (i = 0 ; i < descriptor.bNumConfigurations ; ++i) {
     (void) (*(dpriv->device))->GetConfigurationDescriptorPtr (dpriv->device, i, &cached_configuration);
     if (memcmp (cached_configuration, cached_configurations + i, sizeof (cached_configurations[i]))) {
-      usbi_dbg ("darwin/reset_device: configuration descriptor %d changed", i);
+      usbi_dbg (ctx, "darwin/reenumerate_device: configuration descriptor %d changed", i);
       return LIBUSB_ERROR_NOT_FOUND;
     }
   }
 
-  usbi_dbg ("darwin/reset_device: device reset complete. restoring state...");
+  usbi_dbg (ctx, "darwin/reenumerate_device: device reset complete. restoring state...");
 
   return darwin_restore_state (dev_handle, active_config, claimed_interfaces);
 }
 
+static int darwin_reset_device (struct libusb_device_handle *dev_handle) {
+  struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev);
+  IOReturn kresult;
+  enum libusb_error ret;
+
+#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);
+    ret = darwin_to_libusb (kresult);
+  } else {
+    ret = darwin_reenumerate_device (dev_handle, false);
+  }
+#else
+  /* ResetDevice() is missing on non-macOS platforms */
+  ret = darwin_reenumerate_device (dev_handle, false);
+  if ((ret == LIBUSB_SUCCESS || ret == LIBUSB_ERROR_NOT_FOUND) && dpriv->capture_count > 0) {
+    int capture_count;
+    int8_t active_config = dpriv->active_config;
+    unsigned long claimed_interfaces = dev_handle->claimed_interfaces;
+
+    /* save old capture_count */
+    capture_count = dpriv->capture_count;
+    /* reset capture count */
+    dpriv->capture_count = 0;
+    /* attempt to detach kernel driver again as it is now re-attached */
+    ret = darwin_detach_kernel_driver (dev_handle, 0);
+    if (ret != LIBUSB_SUCCESS) {
+      return ret;
+    }
+    /* restore capture_count */
+    dpriv->capture_count = capture_count;
+    /* restore configuration */
+    ret = darwin_restore_state (dev_handle, active_config, claimed_interfaces);
+  }
+#endif
+  return ret;
+}
+
+static io_service_t usb_find_interface_matching_location (const io_name_t class_name, UInt8 interface_number, UInt32 location) {
+  CFMutableDictionaryRef matchingDict = IOServiceMatching (class_name);
+  CFMutableDictionaryRef propertyMatchDict = CFDictionaryCreateMutable (kCFAllocatorDefault, 0,
+                                                                        &kCFTypeDictionaryKeyCallBacks,
+                                                                        &kCFTypeDictionaryValueCallBacks);
+  CFTypeRef locationCF = CFNumberCreate (NULL, kCFNumberSInt32Type, &location);
+  CFTypeRef interfaceCF =  CFNumberCreate (NULL, kCFNumberSInt8Type, &interface_number);
+
+  CFDictionarySetValue (matchingDict, CFSTR(kIOPropertyMatchKey), propertyMatchDict);
+  CFDictionarySetValue (propertyMatchDict, CFSTR(kUSBDevicePropertyLocationID), locationCF);
+  CFDictionarySetValue (propertyMatchDict, CFSTR(kUSBHostMatchingPropertyInterfaceNumber), interfaceCF);
+
+  CFRelease (interfaceCF);
+  CFRelease (locationCF);
+  CFRelease (propertyMatchDict);
+
+  return IOServiceGetMatchingService (darwin_default_master_port, matchingDict);
+}
+
 static int darwin_kernel_driver_active(struct libusb_device_handle *dev_handle, uint8_t interface) {
   struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev);
-  io_service_t usbInterface;
-  CFTypeRef driver;
-  IOReturn kresult;
+  io_service_t usb_interface, child = IO_OBJECT_NULL;
 
-  kresult = darwin_get_interface (dpriv->device, interface, &usbInterface);
-  if (kresult != kIOReturnSuccess) {
-    usbi_err (HANDLE_CTX (dev_handle), "darwin_get_interface: %s", darwin_error_str(kresult));
-
-    return darwin_to_libusb (kresult);
+  /* locate the IO registry entry for this interface */
+  usb_interface = usb_find_interface_matching_location (kIOUSBHostInterfaceClassName, interface, dpriv->location);
+  if (0 == usb_interface) {
+    /* check for the legacy class entry */
+    usb_interface = usb_find_interface_matching_location (kIOUSBInterfaceClassName, interface, dpriv->location);
+    if (0 == usb_interface) {
+      return LIBUSB_ERROR_NOT_FOUND;
+    }
   }
 
-  driver = IORegistryEntryCreateCFProperty (usbInterface, kIOBundleIdentifierKey, kCFAllocatorDefault, 0);
-  IOObjectRelease (usbInterface);
-
-  if (driver) {
-    CFRelease (driver);
-
+  /* if the IO object has a child entry in the IO Registry it has a kernel driver attached */
+  (void) IORegistryEntryGetChildEntry (usb_interface, kIOServicePlane, &child);
+  IOObjectRelease (usb_interface);
+  if (IO_OBJECT_NULL != child) {
+    IOObjectRelease (child);
     return 1;
   }
 
@@ -1873,11 +2061,17 @@
   struct darwin_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer);
 
   IOReturn kresult;
-  uint8_t direction, number, interval, pipeRef, transferType;
-  uint16_t maxPacketSize;
+  uint8_t pipeRef, interval;
   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
 
   struct darwin_interface *cInterface;
 
@@ -1909,8 +2103,20 @@
   }
 
   /* determine the properties of this endpoint and the speed of the device */
-  (*(cInterface->interface))->GetPipeProperties (cInterface->interface, pipeRef, &direction, &number,
+#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
+  if (kresult != kIOReturnSuccess) {
+    usbi_err (TRANSFER_CTX (transfer), "failed to get pipe properties: %d", kresult);
+    free(tpriv->isoc_framelist);
+    tpriv->isoc_framelist = NULL;
+
+    return darwin_to_libusb (kresult);
+  }
 
   /* Last but not least we need the bus frame number */
   kresult = (*(cInterface->interface))->GetBusFrameNumber(cInterface->interface, &frame, &atTime);
@@ -1922,9 +2128,6 @@
     return darwin_to_libusb (kresult);
   }
 
-  (*(cInterface->interface))->GetPipeProperties (cInterface->interface, pipeRef, &direction, &number,
-                                                 &transferType, &maxPacketSize, &interval);
-
   /* schedule for a frame a little in the future */
   frame += 4;
 
@@ -2051,8 +2254,10 @@
   uint8_t pipeRef, iface;
   IOReturn kresult;
 
+  struct libusb_context *ctx = ITRANSFER_CTX (itransfer);
+
   if (ep_to_pipeRef (transfer->dev_handle, transfer->endpoint, &pipeRef, &iface, &cInterface) != 0) {
-    usbi_err (TRANSFER_CTX (transfer), "endpoint not found on any open interface");
+    usbi_err (ctx, "endpoint not found on any open interface");
 
     return LIBUSB_ERROR_NOT_FOUND;
   }
@@ -2060,7 +2265,7 @@
   if (!dpriv->device)
     return LIBUSB_ERROR_NO_DEVICE;
 
-  usbi_warn (ITRANSFER_CTX (itransfer), "aborting all transactions on interface %d pipe %d", iface, pipeRef);
+  usbi_warn (ctx, "aborting all transactions on interface %d pipe %d", iface, pipeRef);
 
   /* abort transactions */
 #if InterfaceVersion >= 550
@@ -2070,7 +2275,7 @@
 #endif
     (*(cInterface->interface))->AbortPipe (cInterface->interface, pipeRef);
 
-  usbi_dbg ("calling clear pipe stall to clear the data toggle bit");
+  usbi_dbg (ctx, "calling clear pipe stall to clear the data toggle bit");
 
   /* newer versions of darwin support clearing additional bits on the device's endpoint */
   kresult = (*(cInterface->interface))->ClearPipeStallBothEnds(cInterface->interface, pipeRef);
@@ -2099,7 +2304,7 @@
   struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
   struct darwin_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer);
 
-  usbi_dbg ("an async io operation has completed");
+  usbi_dbg (TRANSFER_CTX(transfer), "an async io operation has completed");
 
   /* if requested write a zero packet */
   if (kIOReturnSuccess == result && IS_XFEROUT(transfer) && transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET) {
@@ -2122,6 +2327,8 @@
   if (itransfer->timeout_flags & USBI_TRANSFER_TIMED_OUT)
     result = kIOUSBTransactionTimeout;
 
+  struct libusb_context *ctx = ITRANSFER_CTX (itransfer);
+
   switch (result) {
   case kIOReturnUnderrun:
   case kIOReturnSuccess:
@@ -2129,17 +2336,17 @@
   case kIOReturnAborted:
     return LIBUSB_TRANSFER_CANCELLED;
   case kIOUSBPipeStalled:
-    usbi_dbg ("transfer error: pipe is stalled");
+    usbi_dbg (ctx, "transfer error: pipe is stalled");
     return LIBUSB_TRANSFER_STALL;
   case kIOReturnOverrun:
-    usbi_warn (ITRANSFER_CTX (itransfer), "transfer error: data overrun");
+    usbi_warn (ctx, "transfer error: data overrun");
     return LIBUSB_TRANSFER_OVERFLOW;
   case kIOUSBTransactionTimeout:
-    usbi_warn (ITRANSFER_CTX (itransfer), "transfer error: timed out");
+    usbi_warn (ctx, "transfer error: timed out");
     itransfer->timeout_flags |= USBI_TRANSFER_TIMED_OUT;
     return LIBUSB_TRANSFER_TIMED_OUT;
   default:
-    usbi_warn (ITRANSFER_CTX (itransfer), "transfer error: %s (value = 0x%08x)", darwin_error_str (result), result);
+    usbi_warn (ctx, "transfer error: %s (value = 0x%08x)", darwin_error_str (result), result);
     return LIBUSB_TRANSFER_ERROR;
   }
 }
@@ -2148,22 +2355,23 @@
   struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
   struct darwin_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer);
   const unsigned char max_transfer_type = LIBUSB_TRANSFER_TYPE_BULK_STREAM;
-  const char *transfer_types[max_transfer_type + 1] = {"control", "isoc", "bulk", "interrupt", "bulk-stream"};
+  const char *transfer_types[] = {"control", "isoc", "bulk", "interrupt", "bulk-stream", NULL};
   bool is_isoc = LIBUSB_TRANSFER_TYPE_ISOCHRONOUS == transfer->type;
+  struct libusb_context *ctx = ITRANSFER_CTX (itransfer);
 
   if (transfer->type > max_transfer_type) {
-    usbi_err (TRANSFER_CTX(transfer), "unknown endpoint type %d", transfer->type);
+    usbi_err (ctx, "unknown endpoint type %d", transfer->type);
     return LIBUSB_ERROR_INVALID_PARAM;
   }
 
   if (NULL == tpriv) {
-    usbi_err (TRANSFER_CTX(transfer), "malformed request is missing transfer priv");
+    usbi_err (ctx, "malformed request is missing transfer priv");
     return LIBUSB_ERROR_INVALID_PARAM;
   }
 
-  usbi_dbg ("handling transfer completion type %s with kernel status %d", transfer_types[transfer->type], tpriv->result);
+  usbi_dbg (ctx, "handling transfer completion type %s with kernel status %d", transfer_types[transfer->type], tpriv->result);
 
-  if (kIOReturnSuccess == tpriv->result || kIOReturnUnderrun == tpriv->result) {
+  if (kIOReturnSuccess == tpriv->result || kIOReturnUnderrun == tpriv->result || kIOUSBTransactionTimeout == tpriv->result) {
     if (is_isoc && tpriv->isoc_framelist) {
       /* copy isochronous results back */
 
@@ -2262,9 +2470,159 @@
 }
 #endif
 
+#if InterfaceVersion >= 700
+
+/* macOS APIs for getting entitlement values */
+
+#if !defined(TARGET_OS_OSX) || TARGET_OS_OSX == 1
+#include <Security/Security.h>
+#else
+typedef struct __SecTask *SecTaskRef;
+extern SecTaskRef SecTaskCreateFromSelf(CFAllocatorRef allocator);
+extern CFTypeRef SecTaskCopyValueForEntitlement(SecTaskRef task, CFStringRef entitlement, CFErrorRef *error);
+#endif
+
+static bool darwin_has_capture_entitlements (void) {
+  SecTaskRef task;
+  CFTypeRef value;
+  bool entitled;
+
+  task = SecTaskCreateFromSelf (kCFAllocatorDefault);
+  if (task == NULL) {
+    return false;
+  }
+  value = SecTaskCopyValueForEntitlement(task, CFSTR("com.apple.vm.device-access"), NULL);
+  CFRelease (task);
+  entitled = value && (CFGetTypeID (value) == CFBooleanGetTypeID ()) && CFBooleanGetValue (value);
+  if (value) {
+    CFRelease (value);
+  }
+  return entitled;
+}
+
+static int darwin_reload_device (struct libusb_device_handle *dev_handle) {
+  struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev);
+  enum libusb_error err;
+
+  usbi_mutex_lock(&darwin_cached_devices_lock);
+  (*(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;
+  }
+  usbi_mutex_unlock(&darwin_cached_devices_lock);
+
+  return err;
+}
+
+/* On macOS, we capture an entire device at once, not individual interfaces. */
+
+static int darwin_detach_kernel_driver (struct libusb_device_handle *dev_handle, uint8_t interface) {
+  UNUSED(interface);
+  struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev);
+  IOReturn kresult;
+  enum libusb_error err;
+  struct libusb_context *ctx = HANDLE_CTX (dev_handle);
+
+  if (HAS_CAPTURE_DEVICE()) {
+  } else {
+    return LIBUSB_ERROR_NOT_SUPPORTED;
+  }
+
+  if (dpriv->capture_count == 0) {
+    usbi_dbg (ctx, "attempting to detach kernel driver from device");
+
+    if (darwin_has_capture_entitlements ()) {
+      /* request authorization */
+      kresult = IOServiceAuthorize (dpriv->service, kIOServiceInteractionAllowed);
+      if (kresult != kIOReturnSuccess) {
+        usbi_warn (ctx, "IOServiceAuthorize: %s", darwin_error_str(kresult));
+        return darwin_to_libusb (kresult);
+      }
+
+      /* we need start() to be called again for authorization status to refresh */
+      err = darwin_reload_device (dev_handle);
+      if (err != LIBUSB_SUCCESS) {
+        return err;
+      }
+    } else {
+      usbi_info (ctx, "no capture entitlements. may not be able to detach the kernel driver for this device");
+      if (0 != geteuid()) {
+        usbi_warn (ctx, "USB device capture requires either an entitlement (com.apple.vm.device-access) or root privilege");
+        return LIBUSB_ERROR_ACCESS;
+      }
+    }
+
+    /* reset device to release existing drivers */
+    err = darwin_reenumerate_device (dev_handle, true);
+    if (err != LIBUSB_SUCCESS) {
+      return err;
+    }
+  }
+  dpriv->capture_count++;
+  return LIBUSB_SUCCESS;
+}
+
+
+static int darwin_attach_kernel_driver (struct libusb_device_handle *dev_handle, uint8_t interface) {
+  UNUSED(interface);
+  struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev);
+
+  if (HAS_CAPTURE_DEVICE()) {
+  } else {
+    return LIBUSB_ERROR_NOT_SUPPORTED;
+  }
+
+  dpriv->capture_count--;
+  if (dpriv->capture_count > 0) {
+    return LIBUSB_SUCCESS;
+  }
+
+  usbi_dbg (HANDLE_CTX (dev_handle), "reenumerating device for kernel driver attach");
+
+  /* reset device to attach kernel drivers */
+  return darwin_reenumerate_device (dev_handle, false);
+}
+
+static int darwin_capture_claim_interface(struct libusb_device_handle *dev_handle, uint8_t iface) {
+  enum libusb_error ret;
+  if (dev_handle->auto_detach_kernel_driver && darwin_kernel_driver_active(dev_handle, iface)) {
+    ret = darwin_detach_kernel_driver (dev_handle, iface);
+    if (ret != LIBUSB_SUCCESS) {
+      usbi_info (HANDLE_CTX (dev_handle), "failed to auto-detach the kernel driver for this device, ret=%d", ret);
+    }
+  }
+
+  return darwin_claim_interface (dev_handle, iface);
+}
+
+static int darwin_capture_release_interface(struct libusb_device_handle *dev_handle, uint8_t iface) {
+  enum libusb_error ret;
+  struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev);
+
+  ret = darwin_release_interface (dev_handle, iface);
+  if (ret != LIBUSB_SUCCESS) {
+    return ret;
+  }
+
+  if (dev_handle->auto_detach_kernel_driver && dpriv->capture_count > 0) {
+    ret = darwin_attach_kernel_driver (dev_handle, iface);
+    if (LIBUSB_SUCCESS != ret) {
+      usbi_info (HANDLE_CTX (dev_handle), "on attempt to reattach the kernel driver got ret=%d", ret);
+    }
+    /* ignore the error as the interface was successfully released */
+  }
+
+  return LIBUSB_SUCCESS;
+}
+
+#endif
+
 const struct usbi_os_backend usbi_backend = {
         .name = "Darwin",
-        .caps = 0,
+        .caps = USBI_CAP_SUPPORTS_DETACH_KERNEL_DRIVER,
         .init = darwin_init,
         .exit = darwin_exit,
         .get_active_config_descriptor = darwin_get_active_config_descriptor,
@@ -2275,8 +2633,6 @@
         .close = darwin_close,
         .get_configuration = darwin_get_configuration,
         .set_configuration = darwin_set_configuration,
-        .claim_interface = darwin_claim_interface,
-        .release_interface = darwin_release_interface,
 
         .set_interface_altsetting = darwin_set_interface_altsetting,
         .clear_halt = darwin_clear_halt,
@@ -2289,6 +2645,16 @@
 
         .kernel_driver_active = darwin_kernel_driver_active,
 
+#if InterfaceVersion >= 700
+        .detach_kernel_driver = darwin_detach_kernel_driver,
+        .attach_kernel_driver = darwin_attach_kernel_driver,
+        .claim_interface = darwin_capture_claim_interface,
+        .release_interface = darwin_capture_release_interface,
+#else
+        .claim_interface = darwin_claim_interface,
+        .release_interface = darwin_release_interface,
+#endif
+
         .destroy_device = darwin_destroy_device,
 
         .submit_transfer = darwin_submit_transfer,
diff --git a/libusb/os/darwin_usb.h b/libusb/os/darwin_usb.h
index b799bfd..7b72fff 100644
--- a/libusb/os/darwin_usb.h
+++ b/libusb/os/darwin_usb.h
@@ -30,59 +30,63 @@
 #include <IOKit/usb/IOUSBLib.h>
 #include <IOKit/IOCFPlugIn.h>
 
+#if defined(HAVE_IOKIT_USB_IOUSBHOSTFAMILYDEFINITIONS_H)
+#include <IOKit/usb/IOUSBHostFamilyDefinitions.h>
+#endif
+
 /* IOUSBInterfaceInferface */
 
 /* New in OS 10.12.0. */
-#if defined (kIOUSBInterfaceInterfaceID800) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 101200)
+#if defined (kIOUSBInterfaceInterfaceID800)
 
 #define usb_interface_t IOUSBInterfaceInterface800
 #define InterfaceInterfaceID kIOUSBInterfaceInterfaceID800
 #define InterfaceVersion 800
 
 /* New in OS 10.10.0. */
-#elif defined (kIOUSBInterfaceInterfaceID700) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 101000)
+#elif defined (kIOUSBInterfaceInterfaceID700)
 
 #define usb_interface_t IOUSBInterfaceInterface700
 #define InterfaceInterfaceID kIOUSBInterfaceInterfaceID700
 #define InterfaceVersion 700
 
 /* New in OS 10.9.0. */
-#elif defined (kIOUSBInterfaceInterfaceID650) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1090)
+#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) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1090)
+#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) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1080)
+#elif defined (kIOUSBInterfaceInterfaceID500)
 
 #define usb_interface_t IOUSBInterfaceInterface500
 #define InterfaceInterfaceID kIOUSBInterfaceInterfaceID500
 #define InterfaceVersion 500
 
 /* New in OS 10.5.0. */
-#elif defined (kIOUSBInterfaceInterfaceID300) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1050)
+#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) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1050)
+#elif defined (kIOUSBInterfaceInterfaceID245)
 
 #define usb_interface_t IOUSBInterfaceInterface245
 #define InterfaceInterfaceID kIOUSBInterfaceInterfaceID245
 #define InterfaceVersion 245
 
 /* New in OS 10.4.0. */
-#elif defined (kIOUSBInterfaceInterfaceID220) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1040)
+#elif defined (kIOUSBInterfaceInterfaceID220)
 
 #define usb_interface_t IOUSBInterfaceInterface220
 #define InterfaceInterfaceID kIOUSBInterfaceInterfaceID220
@@ -97,42 +101,42 @@
 /* IOUSBDeviceInterface */
 
 /* New in OS 10.9.0. */
-#if defined (kIOUSBDeviceInterfaceID650) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1090)
+#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) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1080)
+#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) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1060)
+#elif defined (kIOUSBDeviceInterfaceID320)
 
 #define usb_device_t    IOUSBDeviceInterface320
 #define DeviceInterfaceID kIOUSBDeviceInterfaceID320
 #define DeviceVersion 320
 
 /* New in OS 10.5.0. */
-#elif defined (kIOUSBDeviceInterfaceID300) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1050)
+#elif defined (kIOUSBDeviceInterfaceID300)
 
 #define usb_device_t    IOUSBDeviceInterface300
 #define DeviceInterfaceID kIOUSBDeviceInterfaceID300
 #define DeviceVersion 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 (kIOUSBDeviceInterfaceID245) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1050)
+#elif defined (kIOUSBDeviceInterfaceID245)
 
 #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) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1030)
+#elif defined (kIOUSBDeviceInterfaceID197)
 
 #define usb_device_t    IOUSBDeviceInterface197
 #define DeviceInterfaceID kIOUSBDeviceInterfaceID197
@@ -144,10 +148,28 @@
 
 #endif
 
+#if !defined(kIOUSBHostInterfaceClassName)
+#define kIOUSBHostInterfaceClassName "IOUSBHostInterface"
+#endif
+
+#if !defined(kUSBHostMatchingPropertyInterfaceNumber)
+#define kUSBHostMatchingPropertyInterfaceNumber "bInterfaceNumber"
+#endif
+
 #if !defined(IO_OBJECT_NULL)
 #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
+
 typedef IOCFPlugInInterface *io_cf_plugin_ref_t;
 typedef IONotificationPortRef io_notification_port_t;
 
@@ -161,11 +183,13 @@
   USBDeviceAddress      address;
   char                  sys_path[21];
   usb_device_t        **device;
+  io_service_t          service;
   int                   open_count;
   UInt8                 first_config, active_config, port;
   int                   can_enumerate;
   int                   refcount;
   bool                  in_reenumerate;
+  int                   capture_count;
 };
 
 struct darwin_device_priv {
diff --git a/libusb/os/events_posix.c b/libusb/os/events_posix.c
index b74189b..715a2d5 100644
--- a/libusb/os/events_posix.c
+++ b/libusb/os/events_posix.c
@@ -222,9 +222,9 @@
 	usbi_nfds_t nfds = (usbi_nfds_t)ctx->event_data_cnt;
 	int internal_fds, num_ready;
 
-	usbi_dbg("poll() %u fds with timeout in %dms", (unsigned int)nfds, timeout_ms);
+	usbi_dbg(ctx, "poll() %u fds with timeout in %dms", (unsigned int)nfds, timeout_ms);
 	num_ready = poll(fds, nfds, timeout_ms);
-	usbi_dbg("poll() returned %d", num_ready);
+	usbi_dbg(ctx, "poll() returned %d", num_ready);
 	if (num_ready == 0) {
 		if (usbi_using_timer(ctx))
 			goto done;
@@ -279,7 +279,7 @@
 					continue;
 				/* pollfd was removed between the creation of the fds array and
 				 * here. remove triggered revent as it is no longer relevant. */
-				usbi_dbg("fd %d was removed, ignoring raised events", fds[n].fd);
+				usbi_dbg(ctx, "fd %d was removed, ignoring raised events", fds[n].fd);
 				fds[n].revents = 0;
 				num_ready--;
 				break;
diff --git a/libusb/os/events_windows.c b/libusb/os/events_windows.c
index 81d8b87..f22bebc 100644
--- a/libusb/os/events_windows.c
+++ b/libusb/os/events_windows.c
@@ -171,9 +171,9 @@
 	DWORD num_handles = (DWORD)ctx->event_data_cnt;
 	DWORD result;
 
-	usbi_dbg("WaitForMultipleObjects() for %lu HANDLEs with timeout in %dms", ULONG_CAST(num_handles), timeout_ms);
+	usbi_dbg(ctx, "WaitForMultipleObjects() for %lu HANDLEs with timeout in %dms", ULONG_CAST(num_handles), timeout_ms);
 	result = WaitForMultipleObjects(num_handles, handles, FALSE, (DWORD)timeout_ms);
-	usbi_dbg("WaitForMultipleObjects() returned %lu", ULONG_CAST(result));
+	usbi_dbg(ctx, "WaitForMultipleObjects() returned %lu", ULONG_CAST(result));
 	if (result == WAIT_TIMEOUT) {
 		if (usbi_using_timer(ctx))
 			goto done;
diff --git a/libusb/os/haiku_pollfs.cpp b/libusb/os/haiku_pollfs.cpp
index cb4fda8..b85edf7 100644
--- a/libusb/os/haiku_pollfs.cpp
+++ b/libusb/os/haiku_pollfs.cpp
@@ -100,14 +100,14 @@
 			for_each_context(ctx) {
 				struct libusb_device *dev = usbi_get_device_by_session_id(ctx, session_id);
 				if (dev) {
-					usbi_dbg("using previously allocated device with location %lu", session_id);
+					usbi_dbg(NULL, "using previously allocated device with location %lu", session_id);
 					libusb_unref_device(dev);
 					continue;
 				}
-				usbi_dbg("allocating new device with location %lu", session_id);
+				usbi_dbg(NULL, "allocating new device with location %lu", session_id);
 				dev = usbi_alloc_device(ctx, session_id);
 				if (!dev) {
-					usbi_dbg("device allocation failed");
+					usbi_dbg(NULL, "device allocation failed");
 					continue;
 				}
 				*((USBDevice **)usbi_get_device_priv(dev)) = fDevice;
@@ -134,7 +134,7 @@
 				usbi_localize_device_descriptor(&dev->device_descriptor);
 
 				if (usbi_sanitize_device(dev) < 0) {
-					usbi_dbg("device sanitization failed");
+					usbi_dbg(NULL, "device sanitization failed");
 					libusb_unref_device(dev);
 					continue;
 				}
@@ -178,7 +178,7 @@
 				usbi_disconnect_device(dev);
 				libusb_unref_device(dev);
 			} else {
-				usbi_dbg("device with location %lu not found", session_id);
+				usbi_dbg(ctx, "device with location %lu not found", session_id);
 			}
 		}
 		usbi_mutex_static_unlock(&active_contexts_lock);
diff --git a/libusb/os/haiku_usb_backend.cpp b/libusb/os/haiku_usb_backend.cpp
index 8bbf3e0..2fcefdd 100644
--- a/libusb/os/haiku_usb_backend.cpp
+++ b/libusb/os/haiku_usb_backend.cpp
@@ -296,16 +296,16 @@
 		return _errno_to_libusb(command.alternate.status);
 	}
 	if (command.alternate.alternate_info == (uint32)alt) {
-		usbi_dbg("Setting alternate interface successful");
+		usbi_dbg(NULL, "Setting alternate interface successful");
 		return LIBUSB_SUCCESS;
 	}
 	command.alternate.alternate_info = alt;
 	if (ioctl(fRawFD, B_USB_RAW_COMMAND_SET_ALT_INTERFACE, &command, sizeof(command)) ||
-			command.alternate.status != B_USB_RAW_STATUS_SUCCESS) { //IF IOCTL FAILS DEVICE DISONNECTED PROBABLY
+			command.alternate.status != B_USB_RAW_STATUS_SUCCESS) { //IF IOCTL FAILS DEVICE DISCONNECTED PROBABLY
 		usbi_err(NULL, "Error setting alternate interface");
 		return _errno_to_libusb(command.alternate.status);
 	}
-	usbi_dbg("Setting alternate interface successful");
+	usbi_dbg(NULL, "Setting alternate interface successful");
 	return LIBUSB_SUCCESS;
 }
 
diff --git a/libusb/os/linux_netlink.c b/libusb/os/linux_netlink.c
index 77c83c5..899084f 100644
--- a/libusb/os/linux_netlink.c
+++ b/libusb/os/linux_netlink.c
@@ -34,8 +34,8 @@
 #ifdef HAVE_ASM_TYPES_H
 #include <asm/types.h>
 #endif
-#include <linux/netlink.h>
 #include <sys/socket.h>
+#include <linux/netlink.h>
 
 #define NL_GROUP_KERNEL 1
 
@@ -99,7 +99,7 @@
 
 	linux_netlink_socket = socket(PF_NETLINK, socktype, NETLINK_KOBJECT_UEVENT);
 	if (linux_netlink_socket == -1 && errno == EINVAL) {
-		usbi_dbg("failed to create netlink socket of type %d, attempting SOCK_RAW", socktype);
+		usbi_dbg(NULL, "failed to create netlink socket of type %d, attempting SOCK_RAW", socktype);
 		socktype = SOCK_RAW;
 		linux_netlink_socket = socket(PF_NETLINK, socktype, NETLINK_KOBJECT_UEVENT);
 	}
@@ -204,7 +204,7 @@
 	} else if (strcmp(tmp, "remove") == 0) {
 		*detached = 1;
 	} else if (strcmp(tmp, "add") != 0) {
-		usbi_dbg("unknown device action %s", tmp);
+		usbi_dbg(NULL, "unknown device action %s", tmp);
 		return -1;
 	}
 
@@ -311,20 +311,20 @@
 	}
 
 	if (sa_nl.nl_groups != NL_GROUP_KERNEL || sa_nl.nl_pid != 0) {
-		usbi_dbg("ignoring netlink message from unknown group/PID (%u/%u)",
+		usbi_dbg(NULL, "ignoring netlink message from unknown group/PID (%u/%u)",
 			 (unsigned int)sa_nl.nl_groups, (unsigned int)sa_nl.nl_pid);
 		return -1;
 	}
 
 	cmsg = CMSG_FIRSTHDR(&msg);
 	if (!cmsg || cmsg->cmsg_type != SCM_CREDENTIALS) {
-		usbi_dbg("ignoring netlink message with no sender credentials");
+		usbi_dbg(NULL, "ignoring netlink message with no sender credentials");
 		return -1;
 	}
 
 	cred = (struct ucred *)CMSG_DATA(cmsg);
 	if (cred->uid != 0) {
-		usbi_dbg("ignoring netlink message with non-zero sender UID %u", (unsigned int)cred->uid);
+		usbi_dbg(NULL, "ignoring netlink message with non-zero sender UID %u", (unsigned int)cred->uid);
 		return -1;
 	}
 
@@ -332,7 +332,7 @@
 	if (r)
 		return r;
 
-	usbi_dbg("netlink hotplug found device busnum: %hhu, devaddr: %hhu, sys_name: %s, removed: %s",
+	usbi_dbg(NULL, "netlink hotplug found device busnum: %hhu, devaddr: %hhu, sys_name: %s, removed: %s",
 		 busnum, devaddr, sys_name, detached ? "yes" : "no");
 
 	/* signal device is available (or not) to all contexts */
@@ -362,7 +362,7 @@
 		usbi_warn(NULL, "failed to set hotplug event thread name, error=%d", r);
 #endif
 
-	usbi_dbg("netlink event thread entering");
+	usbi_dbg(NULL, "netlink event thread entering");
 
 	while (1) {
 		r = poll(fds, 2, -1);
@@ -384,7 +384,7 @@
 		}
 	}
 
-	usbi_dbg("netlink event thread exiting");
+	usbi_dbg(NULL, "netlink event thread exiting");
 
 	return NULL;
 }
diff --git a/libusb/os/linux_udev.c b/libusb/os/linux_udev.c
index beb2f05..9ec9eb1 100644
--- a/libusb/os/linux_udev.c
+++ b/libusb/os/linux_udev.c
@@ -177,7 +177,7 @@
 		usbi_warn(NULL, "failed to set hotplug event thread name, error=%d", r);
 #endif
 
-	usbi_dbg("udev event thread entering");
+	usbi_dbg(NULL, "udev event thread entering");
 
 	while (1) {
 		r = poll(fds, 2, -1);
@@ -201,7 +201,7 @@
 		}
 	}
 
-	usbi_dbg("udev event thread exiting");
+	usbi_dbg(NULL, "udev event thread exiting");
 
 	return NULL;
 }
@@ -246,7 +246,7 @@
 			break;
 		}
 
-		usbi_dbg("udev hotplug event. action: %s.", udev_action);
+		usbi_dbg(NULL, "udev hotplug event. action: %s.", udev_action);
 
 		if (strncmp(udev_action, "add", 3) == 0) {
 			linux_hotplug_enumerate(busnum, devaddr, sys_name);
@@ -313,7 +313,7 @@
 	do {
 		udev_dev = udev_monitor_receive_device(udev_monitor);
 		if (udev_dev) {
-			usbi_dbg("Handling hotplug event from hotplug_poll");
+			usbi_dbg(NULL, "Handling hotplug event from hotplug_poll");
 			udev_hotplug_event(udev_dev);
 		}
 	} while (udev_dev);
diff --git a/libusb/os/linux_usbfs.c b/libusb/os/linux_usbfs.c
index fb2ed53..285d9ca 100644
--- a/libusb/os/linux_usbfs.c
+++ b/libusb/os/linux_usbfs.c
@@ -95,13 +95,9 @@
 /* how many times have we initted (and not exited) ? */
 static int init_count = 0;
 
-#ifdef __ANDROID__
 /* have no authority to operate usb device directly */
-static int weak_authority = 0;
-#endif
+static int no_enumeration = 0;
 
-/* Serialize hotplug start/stop */
-static usbi_mutex_static_t linux_hotplug_startstop_lock = USBI_MUTEX_INITIALIZER;
 /* Serialize scan-devices, event-thread, and poll */
 usbi_mutex_static_t linux_hotplug_lock = USBI_MUTEX_INITIALIZER;
 
@@ -128,7 +124,7 @@
 	void *descriptors;
 	size_t descriptors_len;
 	struct config_descriptor *config_descriptors;
-	uint8_t active_config; /* cache val for !sysfs_available  */
+	int active_config; /* cache val for !sysfs_available  */
 };
 
 struct linux_device_handle_priv {
@@ -169,6 +165,21 @@
 	int iso_packet_offset;
 };
 
+static int dev_has_config0(struct libusb_device *dev)
+{
+	struct linux_device_priv *priv = usbi_get_device_priv(dev);
+	struct config_descriptor *config;
+	uint8_t idx;
+
+	for (idx = 0; idx < dev->device_descriptor.bNumConfigurations; idx++) {
+		config = &priv->config_descriptors[idx];
+		if (config->desc->bConfigurationValue == 0)
+			return 1;
+	}
+
+	return 0;
+}
+
 static int get_usbfs_fd(struct libusb_device *dev, mode_t mode, int silent)
 {
 	struct libusb_context *ctx = DEVICE_CTX(dev);
@@ -223,11 +234,11 @@
 	if (sscanf(name, "usbdev%d.%d", &busnum, &devnum) != 2)
 		return 0;
 	if (busnum < 0 || busnum > UINT8_MAX || devnum < 0 || devnum > UINT8_MAX) {
-		usbi_dbg("invalid usbdev format '%s'", name);
+		usbi_dbg(NULL, "invalid usbdev format '%s'", name);
 		return 0;
 	}
 
-	usbi_dbg("found: %s", name);
+	usbi_dbg(NULL, "found: %s", name);
 	if (bus_p)
 		*bus_p = (uint8_t)busnum;
 	if (dev_p)
@@ -312,7 +323,7 @@
 	if (atoms < 3)
 		ver->sublevel = -1;
 
-	usbi_dbg("reported kernel version is %s", uts.release);
+	usbi_dbg(ctx, "reported kernel version is %s", uts.release);
 
 	return 0;
 }
@@ -360,7 +371,7 @@
 		return LIBUSB_ERROR_OTHER;
 	}
 
-	usbi_dbg("found usbfs at %s", usbfs_path);
+	usbi_dbg(ctx, "found usbfs at %s", usbfs_path);
 
 	if (!max_iso_packet_len) {
 		if (kernel_version_ge(&kversion, 5, 2, 0))
@@ -371,14 +382,14 @@
 			max_iso_packet_len = 8192;
 	}
 
-	usbi_dbg("max iso packet length is (likely) %u bytes", max_iso_packet_len);
+	usbi_dbg(ctx, "max iso packet length is (likely) %u bytes", max_iso_packet_len);
 
 	if (sysfs_available == -1) {
 		struct statfs statfsbuf;
 
 		r = statfs(SYSFS_MOUNT_PATH, &statfsbuf);
 		if (r == 0 && statfsbuf.f_type == SYSFS_MAGIC) {
-			usbi_dbg("sysfs is available");
+			usbi_dbg(ctx, "sysfs is available");
 			sysfs_available = 1;
 		} else {
 			usbi_warn(ctx, "sysfs not mounted");
@@ -386,13 +397,10 @@
 		}
 	}
 
-#ifdef __ANDROID__
-	if (weak_authority) {
+	if (no_enumeration) {
 		return LIBUSB_SUCCESS;
 	}
-#endif
 
-	usbi_mutex_static_lock(&linux_hotplug_startstop_lock);
 	r = LIBUSB_SUCCESS;
 	if (init_count == 0) {
 		/* start up hotplug event handler */
@@ -407,7 +415,6 @@
 	} else {
 		usbi_err(ctx, "error starting hotplug event monitor");
 	}
-	usbi_mutex_static_unlock(&linux_hotplug_startstop_lock);
 
 	return r;
 }
@@ -415,13 +422,16 @@
 static void op_exit(struct libusb_context *ctx)
 {
 	UNUSED(ctx);
-	usbi_mutex_static_lock(&linux_hotplug_startstop_lock);
+
+	if (no_enumeration) {
+		return;
+	}
+
 	assert(init_count != 0);
 	if (!--init_count) {
 		/* tear down event handler */
 		linux_stop_event_monitor();
 	}
-	usbi_mutex_static_unlock(&linux_hotplug_startstop_lock);
 }
 
 static int op_set_option(struct libusb_context *ctx, enum libusb_option option, va_list ap)
@@ -429,15 +439,11 @@
 	UNUSED(ctx);
 	UNUSED(ap);
 
-#ifdef __ANDROID__
-	if (option == LIBUSB_OPTION_WEAK_AUTHORITY) {
-		usbi_dbg("set libusb has weak authority");
-		weak_authority = 1;
+	if (option == LIBUSB_OPTION_NO_DEVICE_DISCOVERY) {
+		usbi_dbg(ctx, "no enumeration will be performed");
+		no_enumeration = 1;
 		return LIBUSB_SUCCESS;
 	}
-#else
-	UNUSED(option);
-#endif
 
 	return LIBUSB_ERROR_NOT_SUPPORTED;
 }
@@ -498,7 +504,7 @@
 	if (fd < 0)
 		return fd;
 
-	r = read(fd, buf, sizeof(buf));
+	r = read(fd, buf, sizeof(buf) - 1);
 	if (r < 0) {
 		r = errno;
 		close(fd);
@@ -516,16 +522,18 @@
 		return 0;
 	}
 
-	/* The kernel does *not* NULL-terminate the string, but every attribute
+	/* The kernel does *not* NUL-terminate the string, but every attribute
 	 * should be terminated with a newline character. */
 	if (!isdigit(buf[0])) {
 		usbi_err(ctx, "attribute %s doesn't have numeric value?", attr);
 		return LIBUSB_ERROR_IO;
 	} else if (buf[r - 1] != '\n') {
-		usbi_err(ctx, "attribute %s doesn't end with newline?", attr);
-		return LIBUSB_ERROR_IO;
+		usbi_warn(ctx, "attribute %s doesn't end with newline?", attr);
+	} else {
+		/* Remove the terminating newline character */
+		r--;
 	}
-	buf[r - 1] = '\0';
+	buf[r] = '\0';
 
 	errno = 0;
 	value = strtol(buf, &endptr, 10);
@@ -565,22 +573,12 @@
 }
 
 /* read the bConfigurationValue for a device */
-static int sysfs_get_active_config(struct libusb_device *dev, uint8_t *config)
+static int sysfs_get_active_config(struct libusb_device *dev, int *config)
 {
 	struct linux_device_priv *priv = usbi_get_device_priv(dev);
-	int ret, tmp;
 
-	ret = read_sysfs_attr(DEVICE_CTX(dev), priv->sysfs_dir, "bConfigurationValue",
-			      UINT8_MAX, &tmp);
-	if (ret < 0)
-		return ret;
-
-	if (tmp == -1)
-		tmp = 0;	/* unconfigured */
-
-	*config = (uint8_t)tmp;
-
-	return 0;
+	return read_sysfs_attr(DEVICE_CTX(dev), priv->sysfs_dir, "bConfigurationValue",
+			UINT8_MAX, config);
 }
 
 int linux_get_device_address(struct libusb_context *ctx, int detached,
@@ -590,7 +588,7 @@
 	int sysfs_val;
 	int r;
 
-	usbi_dbg("getting address for device: %s detached: %d", sys_name, detached);
+	usbi_dbg(ctx, "getting address for device: %s detached: %d", sys_name, detached);
 	/* can't use sysfs to read the bus and device number if the
 	 * device has been detached */
 	if (!sysfs_available || detached || !sys_name) {
@@ -619,7 +617,7 @@
 		return LIBUSB_SUCCESS;
 	}
 
-	usbi_dbg("scan %s", sys_name);
+	usbi_dbg(ctx, "scan %s", sys_name);
 
 	r = read_sysfs_attr(ctx, sys_name, "busnum", UINT8_MAX, &sysfs_val);
 	if (r < 0)
@@ -631,7 +629,7 @@
 		return r;
 	*devaddr = (uint8_t)sysfs_val;
 
-	usbi_dbg("bus=%u dev=%u", *busnum, *devaddr);
+	usbi_dbg(ctx, "bus=%u dev=%u", *busnum, *devaddr);
 
 	return LIBUSB_SUCCESS;
 }
@@ -641,7 +639,12 @@
 	uint8_t *buffer, size_t len)
 {
 	struct usbi_descriptor_header *header;
-	int offset = 0;
+	int offset;
+
+	/* Start seeking past the config descriptor */
+	offset = LIBUSB_DT_CONFIG_SIZE;
+	buffer += LIBUSB_DT_CONFIG_SIZE;
+	len -= LIBUSB_DT_CONFIG_SIZE;
 
 	while (len > 0) {
 		if (len < 2) {
@@ -718,7 +721,7 @@
 		}
 
 		if (priv->sysfs_dir) {
-			 /*
+			/*
 			 * In sysfs wTotalLength is ignored, instead the kernel returns a
 			 * config descriptor with verified bLength fields, with descriptors
 			 * with an invalid bLength removed.
@@ -727,8 +730,7 @@
 			int offset;
 
 			if (num_configs > 1 && idx < num_configs - 1) {
-				offset = seek_to_next_config(ctx, buffer + LIBUSB_DT_CONFIG_SIZE,
-							     remaining - LIBUSB_DT_CONFIG_SIZE);
+				offset = seek_to_next_config(ctx, buffer, remaining);
 				if (offset < 0)
 					return offset;
 				sysfs_config_len = (uint16_t)offset;
@@ -752,6 +754,9 @@
 			}
 		}
 
+		if (config_desc->bConfigurationValue == 0)
+			usbi_warn(ctx, "device has configuration 0");
+
 		priv->config_descriptors[idx].desc = config_desc;
 		priv->config_descriptors[idx].actual_len = config_len;
 
@@ -785,7 +790,7 @@
 {
 	struct linux_device_priv *priv = usbi_get_device_priv(dev);
 	void *config_desc;
-	uint8_t active_config;
+	int active_config;
 	int r;
 
 	if (priv->sysfs_dir) {
@@ -797,12 +802,12 @@
 		active_config = priv->active_config;
 	}
 
-	if (active_config == 0) {
+	if (active_config == -1) {
 		usbi_err(DEVICE_CTX(dev), "device unconfigured");
 		return LIBUSB_ERROR_NOT_FOUND;
 	}
 
-	r = op_get_config_descriptor_by_value(dev, active_config, &config_desc);
+	r = op_get_config_descriptor_by_value(dev, (uint8_t)active_config, &config_desc);
 	if (r < 0)
 		return r;
 
@@ -850,16 +855,25 @@
 
 		/* we hit this error path frequently with buggy devices :( */
 		usbi_warn(DEVICE_CTX(dev), "get configuration failed, errno=%d", errno);
-	} else if (active_config == 0) {
-		/* some buggy devices have a configuration 0, but we're
-		 * reaching into the corner of a corner case here, so let's
-		 * not support buggy devices in these circumstances.
-		 * stick to the specs: a configuration value of 0 means
-		 * unconfigured. */
-		usbi_warn(DEVICE_CTX(dev), "active cfg 0? assuming unconfigured device");
-	}
 
-	priv->active_config = active_config;
+		/* assume the current configuration is the first one if we have
+		 * the configuration descriptors, otherwise treat the device
+		 * as unconfigured. */
+		if (priv->config_descriptors)
+			priv->active_config = (int)priv->config_descriptors[0].desc->bConfigurationValue;
+		else
+			priv->active_config = -1;
+	} else if (active_config == 0) {
+		if (dev_has_config0(dev)) {
+			/* some buggy devices have a configuration 0, but we're
+			 * reaching into the corner of a corner case here. */
+			priv->active_config = 0;
+		} else {
+			priv->active_config = -1;
+		}
+	} else {
+		priv->active_config = (int)active_config;
+	}
 
 	return LIBUSB_SUCCESS;
 }
@@ -991,9 +1005,9 @@
 		usbi_warn(ctx, "Missing rw usbfs access; cannot determine "
 			       "active configuration descriptor");
 		if (priv->config_descriptors)
-			priv->active_config = priv->config_descriptors[0].desc->bConfigurationValue;
+			priv->active_config = (int)priv->config_descriptors[0].desc->bConfigurationValue;
 		else
-			priv->active_config = 0; /* No config dt */
+			priv->active_config = -1; /* No config dt */
 
 		return LIBUSB_SUCCESS;
 	}
@@ -1058,14 +1072,14 @@
 	usbi_mutex_unlock(&ctx->usb_devs_lock);
 
 	if (!dev->parent_dev && add_parent) {
-		usbi_dbg("parent_dev %s not enumerated yet, enumerating now",
+		usbi_dbg(ctx, "parent_dev %s not enumerated yet, enumerating now",
 			 parent_sysfs_dir);
 		sysfs_scan_device(ctx, parent_sysfs_dir);
 		add_parent = 0;
 		goto retry;
 	}
 
-	usbi_dbg("dev %p (%s) has parent %p (%s) port %u", dev, sysfs_dir,
+	usbi_dbg(ctx, "dev %p (%s) has parent %p (%s) port %u", dev, sysfs_dir,
 		 dev->parent_dev, parent_sysfs_dir, dev->port_number);
 
 	free(parent_sysfs_dir);
@@ -1084,17 +1098,17 @@
 	 * will be reused. instead we should add a simple sysfs attribute with
 	 * a session ID. */
 	session_id = busnum << 8 | devaddr;
-	usbi_dbg("busnum %u devaddr %u session_id %lu", busnum, devaddr, session_id);
+	usbi_dbg(ctx, "busnum %u devaddr %u session_id %lu", busnum, devaddr, session_id);
 
 	dev = usbi_get_device_by_session_id(ctx, session_id);
 	if (dev) {
 		/* device already exists in the context */
-		usbi_dbg("session_id %lu already exists", session_id);
+		usbi_dbg(ctx, "session_id %lu already exists", session_id);
 		libusb_unref_device(dev);
 		return LIBUSB_SUCCESS;
 	}
 
-	usbi_dbg("allocating new device for %u/%u (session %lu)",
+	usbi_dbg(ctx, "allocating new device for %u/%u (session %lu)",
 		 busnum, devaddr, session_id);
 	dev = usbi_alloc_device(ctx, session_id);
 	if (!dev)
@@ -1143,7 +1157,7 @@
 			usbi_disconnect_device(dev);
 			libusb_unref_device(dev);
 		} else {
-			usbi_dbg("device not found for session %lx", session_id);
+			usbi_dbg(ctx, "device not found for session %lx", session_id);
 		}
 	}
 	usbi_mutex_static_unlock(&active_contexts_lock);
@@ -1175,7 +1189,7 @@
 	int r = LIBUSB_ERROR_IO;
 
 	sprintf(dirpath, USB_DEVTMPFS_PATH "/%03u", busnum);
-	usbi_dbg("%s", dirpath);
+	usbi_dbg(ctx, "%s", dirpath);
 	dir = opendir(dirpath);
 	if (!dir) {
 		usbi_err(ctx, "opendir '%s' failed, errno=%d", dirpath, errno);
@@ -1191,12 +1205,12 @@
 			continue;
 
 		if (!parse_u8(entry->d_name, &devaddr)) {
-			usbi_dbg("unknown dir entry %s", entry->d_name);
+			usbi_dbg(ctx, "unknown dir entry %s", entry->d_name);
 			continue;
 		}
 
 		if (linux_enumerate_device(ctx, busnum, devaddr, NULL)) {
-			usbi_dbg("failed to enumerate dir entry %s", entry->d_name);
+			usbi_dbg(ctx, "failed to enumerate dir entry %s", entry->d_name);
 			continue;
 		}
 
@@ -1234,12 +1248,12 @@
 
 			r = linux_enumerate_device(ctx, busnum, devaddr, NULL);
 			if (r < 0) {
-				usbi_dbg("failed to enumerate dir entry %s", entry->d_name);
+				usbi_dbg(ctx, "failed to enumerate dir entry %s", entry->d_name);
 				continue;
 			}
 		} else {
 			if (!parse_u8(entry->d_name, &busnum)) {
-				usbi_dbg("unknown dir entry %s", entry->d_name);
+				usbi_dbg(ctx, "unknown dir entry %s", entry->d_name);
 				continue;
 			}
 
@@ -1274,7 +1288,7 @@
 		num_devices++;
 
 		if (sysfs_scan_device(ctx, entry->d_name)) {
-			usbi_dbg("failed to enumerate dir entry %s", entry->d_name);
+			usbi_dbg(ctx, "failed to enumerate dir entry %s", entry->d_name);
 			continue;
 		}
 
@@ -1314,7 +1328,7 @@
 	r = ioctl(fd, IOCTL_USBFS_GET_CAPABILITIES, &hpriv->caps);
 	if (r < 0) {
 		if (errno == ENOTTY)
-			usbi_dbg("getcap not available");
+			usbi_dbg(HANDLE_CTX(handle), "getcap not available");
 		else
 			usbi_err(HANDLE_CTX(handle), "getcap failed, errno=%d", errno);
 		hpriv->caps = USBFS_CAP_BULK_CONTINUATION;
@@ -1348,7 +1362,7 @@
 
 	/* Session id is unused as we do not add the device to the list of
 	 * connected devices. */
-	usbi_dbg("allocating new device for fd %d", fd);
+	usbi_dbg(ctx, "allocating new device for fd %d", fd);
 	dev = usbi_alloc_device(ctx, 0);
 	if (!dev)
 		return LIBUSB_ERROR_NO_MEM;
@@ -1361,7 +1375,7 @@
 		goto out;
 	/* Consider the device as connected, but do not add it to the managed
 	 * device list. */
-	dev->attached = 1;
+	usbi_atomic_store(&dev->attached, 1);
 	handle->dev = dev;
 
 	r = initialize_handle(handle, fd);
@@ -1383,8 +1397,8 @@
 			/* device will still be marked as attached if hotplug monitor thread
 			 * hasn't processed remove event yet */
 			usbi_mutex_static_lock(&linux_hotplug_lock);
-			if (handle->dev->attached) {
-				usbi_dbg("open failed with no device, but device still attached");
+			if (usbi_atomic_load(&handle->dev->attached)) {
+				usbi_dbg(HANDLE_CTX(handle), "open failed with no device, but device still attached");
 				linux_device_disconnected(handle->dev->bus_number,
 							  handle->dev->device_address);
 			}
@@ -1415,22 +1429,27 @@
 	uint8_t *config)
 {
 	struct linux_device_priv *priv = usbi_get_device_priv(handle->dev);
+	int active_config = -1; /* to please compiler */
 	int r;
 
 	if (priv->sysfs_dir) {
-		r = sysfs_get_active_config(handle->dev, config);
+		r = sysfs_get_active_config(handle->dev, &active_config);
 	} else {
 		struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle);
 
 		r = usbfs_get_active_config(handle->dev, hpriv->fd);
 		if (r == LIBUSB_SUCCESS)
-			*config = priv->active_config;
+			active_config = priv->active_config;
 	}
 	if (r < 0)
 		return r;
 
-	if (*config == 0)
-		usbi_err(HANDLE_CTX(handle), "device unconfigured");
+	if (active_config == -1) {
+		usbi_warn(HANDLE_CTX(handle), "device unconfigured");
+		active_config = 0;
+	}
+
+	*config = (uint8_t)active_config;
 
 	return 0;
 }
@@ -1454,11 +1473,13 @@
 		return LIBUSB_ERROR_OTHER;
 	}
 
-	if (config == -1)
-		config = 0;
+	/* if necessary, update our cached active config descriptor */
+	if (!priv->sysfs_dir) {
+		if (config == 0 && !dev_has_config0(handle->dev))
+			config = -1;
 
-	/* update our cached active config descriptor */
-	priv->active_config = (uint8_t)config;
+		priv->active_config = config;
+	}
 
 	return LIBUSB_SUCCESS;
 }
@@ -1847,11 +1868,11 @@
 			continue;
 
 		if (errno == EINVAL) {
-			usbi_dbg("URB not found --> assuming ready to be reaped");
+			usbi_dbg(TRANSFER_CTX(transfer), "URB not found --> assuming ready to be reaped");
 			if (i == (last_plus_one - 1))
 				ret = LIBUSB_ERROR_NOT_FOUND;
 		} else if (errno == ENODEV) {
-			usbi_dbg("Device not found for URB --> assuming ready to be reaped");
+			usbi_dbg(TRANSFER_CTX(transfer), "Device not found for URB --> assuming ready to be reaped");
 			ret = LIBUSB_ERROR_NO_DEVICE;
 		} else {
 			usbi_warn(TRANSFER_CTX(transfer), "unrecognised discard errno %d", errno);
@@ -1942,7 +1963,7 @@
 		last_urb_partial = 1;
 		num_urbs++;
 	}
-	usbi_dbg("need %d urbs for new transfer with length %d", num_urbs, transfer->length);
+	usbi_dbg(TRANSFER_CTX(transfer), "need %d urbs for new transfer with length %d", num_urbs, transfer->length);
 	urbs = calloc(num_urbs, sizeof(*urbs));
 	if (!urbs)
 		return LIBUSB_ERROR_NO_MEM;
@@ -2007,7 +2028,7 @@
 		/* if the first URB submission fails, we can simply free up and
 		 * return failure immediately. */
 		if (i == 0) {
-			usbi_dbg("first URB failed, easy peasy");
+			usbi_dbg(TRANSFER_CTX(transfer), "first URB failed, easy peasy");
 			free(urbs);
 			tpriv->urbs = NULL;
 			return r;
@@ -2041,7 +2062,7 @@
 
 		discard_urbs(itransfer, 0, i);
 
-		usbi_dbg("reporting successful submission but waiting for %d "
+		usbi_dbg(TRANSFER_CTX(transfer), "reporting successful submission but waiting for %d "
 			 "discards before reporting error", i);
 		return 0;
 	}
@@ -2092,7 +2113,7 @@
 	/* usbfs limits the number of iso packets per URB */
 	num_urbs = (num_packets + (MAX_ISO_PACKETS_PER_URB - 1)) / MAX_ISO_PACKETS_PER_URB;
 
-	usbi_dbg("need %d urbs for new transfer with length %d", num_urbs, transfer->length);
+	usbi_dbg(TRANSFER_CTX(transfer), "need %d urbs for new transfer with length %d", num_urbs, transfer->length);
 
 	urbs = calloc(num_urbs, sizeof(*urbs));
 	if (!urbs)
@@ -2163,7 +2184,7 @@
 		/* if the first URB submission fails, we can simply free up and
 		 * return failure immediately. */
 		if (i == 0) {
-			usbi_dbg("first URB failed, easy peasy");
+			usbi_dbg(TRANSFER_CTX(transfer), "first URB failed, easy peasy");
 			free_iso_urbs(tpriv);
 			return r;
 		}
@@ -2188,7 +2209,7 @@
 		tpriv->num_retired = num_urbs - i;
 		discard_urbs(itransfer, 0, i);
 
-		usbi_dbg("reporting successful submission but waiting for %d "
+		usbi_dbg(TRANSFER_CTX(transfer), "reporting successful submission but waiting for %d "
 			 "discards before reporting error", i);
 		return 0;
 	}
@@ -2318,14 +2339,14 @@
 	int urb_idx = urb - tpriv->urbs;
 
 	usbi_mutex_lock(&itransfer->lock);
-	usbi_dbg("handling completion status %d of bulk urb %d/%d", urb->status,
+	usbi_dbg(TRANSFER_CTX(transfer), "handling completion status %d of bulk urb %d/%d", urb->status,
 		 urb_idx + 1, tpriv->num_urbs);
 
 	tpriv->num_retired++;
 
 	if (tpriv->reap_action != NORMAL) {
 		/* cancelled, submit_fail, or completed early */
-		usbi_dbg("abnormal reap: urb status %d", urb->status);
+		usbi_dbg(TRANSFER_CTX(transfer), "abnormal reap: urb status %d", urb->status);
 
 		/* even though we're in the process of cancelling, it's possible that
 		 * we may receive some data in these URBs that we don't want to lose.
@@ -2346,9 +2367,9 @@
 		if (urb->actual_length > 0) {
 			unsigned char *target = transfer->buffer + itransfer->transferred;
 
-			usbi_dbg("received %d bytes of surplus data", urb->actual_length);
+			usbi_dbg(TRANSFER_CTX(transfer), "received %d bytes of surplus data", urb->actual_length);
 			if (urb->buffer != target) {
-				usbi_dbg("moving surplus data from offset %zu to offset %zu",
+				usbi_dbg(TRANSFER_CTX(transfer), "moving surplus data from offset %zu to offset %zu",
 					 (unsigned char *)urb->buffer - transfer->buffer,
 					 target - transfer->buffer);
 				memmove(target, urb->buffer, urb->actual_length);
@@ -2357,7 +2378,7 @@
 		}
 
 		if (tpriv->num_retired == tpriv->num_urbs) {
-			usbi_dbg("abnormal reap: last URB handled, reporting");
+			usbi_dbg(TRANSFER_CTX(transfer), "abnormal reap: last URB handled, reporting");
 			if (tpriv->reap_action != COMPLETED_EARLY &&
 			    tpriv->reap_status == LIBUSB_TRANSFER_COMPLETED)
 				tpriv->reap_status = LIBUSB_TRANSFER_ERROR;
@@ -2381,17 +2402,17 @@
 		break;
 	case -ENODEV:
 	case -ESHUTDOWN:
-		usbi_dbg("device removed");
+		usbi_dbg(TRANSFER_CTX(transfer), "device removed");
 		tpriv->reap_status = LIBUSB_TRANSFER_NO_DEVICE;
 		goto cancel_remaining;
 	case -EPIPE:
-		usbi_dbg("detected endpoint stall");
+		usbi_dbg(TRANSFER_CTX(transfer), "detected endpoint stall");
 		if (tpriv->reap_status == LIBUSB_TRANSFER_COMPLETED)
 			tpriv->reap_status = LIBUSB_TRANSFER_STALL;
 		goto cancel_remaining;
 	case -EOVERFLOW:
 		/* overflow can only ever occur in the last urb */
-		usbi_dbg("overflow, actual_length=%d", urb->actual_length);
+		usbi_dbg(TRANSFER_CTX(transfer), "overflow, actual_length=%d", urb->actual_length);
 		if (tpriv->reap_status == LIBUSB_TRANSFER_COMPLETED)
 			tpriv->reap_status = LIBUSB_TRANSFER_OVERFLOW;
 		goto completed;
@@ -2400,7 +2421,7 @@
 	case -EILSEQ:
 	case -ECOMM:
 	case -ENOSR:
-		usbi_dbg("low-level bus error %d", urb->status);
+		usbi_dbg(TRANSFER_CTX(transfer), "low-level bus error %d", urb->status);
 		tpriv->reap_action = ERROR;
 		goto cancel_remaining;
 	default:
@@ -2412,10 +2433,10 @@
 	/* if we've reaped all urbs or we got less data than requested then we're
 	 * done */
 	if (tpriv->num_retired == tpriv->num_urbs) {
-		usbi_dbg("all URBs in transfer reaped --> complete!");
+		usbi_dbg(TRANSFER_CTX(transfer), "all URBs in transfer reaped --> complete!");
 		goto completed;
 	} else if (urb->actual_length < urb->buffer_length) {
-		usbi_dbg("short transfer %d/%d --> complete!",
+		usbi_dbg(TRANSFER_CTX(transfer), "short transfer %d/%d --> complete!",
 			 urb->actual_length, urb->buffer_length);
 		if (tpriv->reap_action == NORMAL)
 			tpriv->reap_action = COMPLETED_EARLY;
@@ -2471,7 +2492,7 @@
 		return LIBUSB_ERROR_NOT_FOUND;
 	}
 
-	usbi_dbg("handling completion status %d of iso urb %d/%d", urb->status,
+	usbi_dbg(TRANSFER_CTX(transfer), "handling completion status %d of iso urb %d/%d", urb->status,
 		 urb_idx, num_urbs);
 
 	/* copy isochronous results back in */
@@ -2490,15 +2511,15 @@
 			break;
 		case -ENODEV:
 		case -ESHUTDOWN:
-			usbi_dbg("packet %d - device removed", i);
+			usbi_dbg(TRANSFER_CTX(transfer), "packet %d - device removed", i);
 			lib_desc->status = LIBUSB_TRANSFER_NO_DEVICE;
 			break;
 		case -EPIPE:
-			usbi_dbg("packet %d - detected endpoint stall", i);
+			usbi_dbg(TRANSFER_CTX(transfer), "packet %d - detected endpoint stall", i);
 			lib_desc->status = LIBUSB_TRANSFER_STALL;
 			break;
 		case -EOVERFLOW:
-			usbi_dbg("packet %d - overflow error", i);
+			usbi_dbg(TRANSFER_CTX(transfer), "packet %d - overflow error", i);
 			lib_desc->status = LIBUSB_TRANSFER_OVERFLOW;
 			break;
 		case -ETIME:
@@ -2507,7 +2528,7 @@
 		case -ECOMM:
 		case -ENOSR:
 		case -EXDEV:
-			usbi_dbg("packet %d - low-level USB error %d", i, urb_desc->status);
+			usbi_dbg(TRANSFER_CTX(transfer), "packet %d - low-level USB error %d", i, urb_desc->status);
 			lib_desc->status = LIBUSB_TRANSFER_ERROR;
 			break;
 		default:
@@ -2522,10 +2543,10 @@
 	tpriv->num_retired++;
 
 	if (tpriv->reap_action != NORMAL) { /* cancelled or submit_fail */
-		usbi_dbg("CANCEL: urb status %d", urb->status);
+		usbi_dbg(TRANSFER_CTX(transfer), "CANCEL: urb status %d", urb->status);
 
 		if (tpriv->num_retired == num_urbs) {
-			usbi_dbg("CANCEL: last URB handled, reporting");
+			usbi_dbg(TRANSFER_CTX(transfer), "CANCEL: last URB handled, reporting");
 			free_iso_urbs(tpriv);
 			if (tpriv->reap_action == CANCELLED) {
 				usbi_mutex_unlock(&itransfer->lock);
@@ -2545,7 +2566,7 @@
 	case -ECONNRESET:
 		break;
 	case -ESHUTDOWN:
-		usbi_dbg("device removed");
+		usbi_dbg(TRANSFER_CTX(transfer), "device removed");
 		status = LIBUSB_TRANSFER_NO_DEVICE;
 		break;
 	default:
@@ -2556,7 +2577,7 @@
 
 	/* if we've reaped all urbs then we're done */
 	if (tpriv->num_retired == num_urbs) {
-		usbi_dbg("all URBs in transfer reaped --> complete!");
+		usbi_dbg(TRANSFER_CTX(transfer), "all URBs in transfer reaped --> complete!");
 		free_iso_urbs(tpriv);
 		usbi_mutex_unlock(&itransfer->lock);
 		return usbi_handle_transfer_completion(itransfer, status);
@@ -2574,7 +2595,7 @@
 	int status;
 
 	usbi_mutex_lock(&itransfer->lock);
-	usbi_dbg("handling completion status %d", urb->status);
+	usbi_dbg(ITRANSFER_CTX(itransfer), "handling completion status %d", urb->status);
 
 	itransfer->transferred += urb->actual_length;
 
@@ -2597,15 +2618,15 @@
 		break;
 	case -ENODEV:
 	case -ESHUTDOWN:
-		usbi_dbg("device removed");
+		usbi_dbg(ITRANSFER_CTX(itransfer), "device removed");
 		status = LIBUSB_TRANSFER_NO_DEVICE;
 		break;
 	case -EPIPE:
-		usbi_dbg("unsupported control request");
+		usbi_dbg(ITRANSFER_CTX(itransfer), "unsupported control request");
 		status = LIBUSB_TRANSFER_STALL;
 		break;
 	case -EOVERFLOW:
-		usbi_dbg("overflow, actual_length=%d", urb->actual_length);
+		usbi_dbg(ITRANSFER_CTX(itransfer), "overflow, actual_length=%d", urb->actual_length);
 		status = LIBUSB_TRANSFER_OVERFLOW;
 		break;
 	case -ETIME:
@@ -2613,7 +2634,7 @@
 	case -EILSEQ:
 	case -ECOMM:
 	case -ENOSR:
-		usbi_dbg("low-level bus error %d", urb->status);
+		usbi_dbg(ITRANSFER_CTX(itransfer), "low-level bus error %d", urb->status);
 		status = LIBUSB_TRANSFER_ERROR;
 		break;
 	default:
@@ -2650,7 +2671,7 @@
 	itransfer = urb->usercontext;
 	transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
 
-	usbi_dbg("urb type=%u status=%d transferred=%d", urb->type, urb->status, urb->actual_length);
+	usbi_dbg(HANDLE_CTX(handle), "urb type=%u status=%d transferred=%d", urb->type, urb->status, urb->actual_length);
 
 	switch (transfer->type) {
 	case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
@@ -2707,7 +2728,7 @@
 			/* device will still be marked as attached if hotplug monitor thread
 			 * hasn't processed remove event yet */
 			usbi_mutex_static_lock(&linux_hotplug_lock);
-			if (handle->dev->attached)
+			if (usbi_atomic_load(&handle->dev->attached))
 				linux_device_disconnected(handle->dev->bus_number,
 							  handle->dev->device_address);
 			usbi_mutex_static_unlock(&linux_hotplug_lock);
diff --git a/libusb/os/netbsd_usb.c b/libusb/os/netbsd_usb.c
index 7a36209..74833f6 100644
--- a/libusb/os/netbsd_usb.c
+++ b/libusb/os/netbsd_usb.c
@@ -122,7 +122,7 @@
 	char devnode[16];
 	int fd, err, i;
 
-	usbi_dbg(" ");
+	usbi_dbg(ctx, " ");
 
 	/* Only ugen(4) is supported */
 	for (i = 0; i < USB_MAX_DEVICES; i++) {
@@ -205,7 +205,7 @@
 	for (i = 0; i < USB_MAX_ENDPOINTS; i++)
 		hpriv->endpoints[i] = -1;
 
-	usbi_dbg("open %s: fd %d", dpriv->devnode, dpriv->fd);
+	usbi_dbg(HANDLE_CTX(handle), "open %s: fd %d", dpriv->devnode, dpriv->fd);
 
 	return (LIBUSB_SUCCESS);
 }
@@ -215,7 +215,7 @@
 {
 	struct device_priv *dpriv = usbi_get_device_priv(handle->dev);
 
-	usbi_dbg("close: fd %d", dpriv->fd);
+	usbi_dbg(HANDLE_CTX(handle), "close: fd %d", dpriv->fd);
 
 	close(dpriv->fd);
 	dpriv->fd = -1;
@@ -229,7 +229,7 @@
 
 	len = MIN(len, (size_t)UGETW(dpriv->cdesc->wTotalLength));
 
-	usbi_dbg("len %zu", len);
+	usbi_dbg(DEVICE_CTX(dev), "len %zu", len);
 
 	memcpy(buf, dpriv->cdesc, len);
 
@@ -244,7 +244,7 @@
 	struct usb_full_desc ufd;
 	int fd, err;
 
-	usbi_dbg("index %u, len %zu", idx, len);
+	usbi_dbg(DEVICE_CTX(dev), "index %u, len %zu", idx, len);
 
 	/* A config descriptor may be requested before opening the device */
 	if (dpriv->fd >= 0) {
@@ -278,12 +278,12 @@
 	struct device_priv *dpriv = usbi_get_device_priv(handle->dev);
 	int tmp;
 
-	usbi_dbg(" ");
+	usbi_dbg(HANDLE_CTX(handle), " ");
 
 	if (ioctl(dpriv->fd, USB_GET_CONFIG, &tmp) < 0)
 		return _errno_to_libusb(errno);
 
-	usbi_dbg("configuration %d", tmp);
+	usbi_dbg(HANDLE_CTX(handle), "configuration %d", tmp);
 	*config = (uint8_t)tmp;
 
 	return (LIBUSB_SUCCESS);
@@ -294,7 +294,7 @@
 {
 	struct device_priv *dpriv = usbi_get_device_priv(handle->dev);
 
-	usbi_dbg("configuration %d", config);
+	usbi_dbg(HANDLE_CTX(handle), "configuration %d", config);
 
 	if (ioctl(dpriv->fd, USB_SET_CONFIG, &config) < 0)
 		return _errno_to_libusb(errno);
@@ -338,7 +338,7 @@
 	struct device_priv *dpriv = usbi_get_device_priv(handle->dev);
 	struct usb_alt_interface intf;
 
-	usbi_dbg("iface %u, setting %u", iface, altsetting);
+	usbi_dbg(HANDLE_CTX(handle), "iface %u, setting %u", iface, altsetting);
 
 	memset(&intf, 0, sizeof(intf));
 
@@ -357,7 +357,7 @@
 	struct device_priv *dpriv = usbi_get_device_priv(handle->dev);
 	struct usb_ctl_request req;
 
-	usbi_dbg(" ");
+	usbi_dbg(HANDLE_CTX(handle), " ");
 
 	req.ucr_request.bmRequestType = UT_WRITE_ENDPOINT;
 	req.ucr_request.bRequest = UR_CLEAR_FEATURE;
@@ -376,7 +376,7 @@
 {
 	struct device_priv *dpriv = usbi_get_device_priv(dev);
 
-	usbi_dbg(" ");
+	usbi_dbg(DEVICE_CTX(dev), " ");
 
 	free(dpriv->cdesc);
 }
@@ -387,7 +387,7 @@
 	struct libusb_transfer *transfer;
 	int err = 0;
 
-	usbi_dbg(" ");
+	usbi_dbg(ITRANSFER_CTX(itransfer), " ");
 
 	transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
 
@@ -430,7 +430,7 @@
 {
 	UNUSED(itransfer);
 
-	usbi_dbg(" ");
+	usbi_dbg(ITRANSFER_CTX(itransfer), " ");
 
 	return (LIBUSB_ERROR_NOT_SUPPORTED);
 }
@@ -458,7 +458,7 @@
 		return (LIBUSB_ERROR_TIMEOUT);
 	}
 
-	usbi_dbg("error: %s", strerror(err));
+	usbi_dbg(NULL, "error: %s", strerror(err));
 
 	return (LIBUSB_ERROR_OTHER);
 }
@@ -472,14 +472,14 @@
 	void *buf;
 	int len;
 
-	usbi_dbg("fd %d", fd);
+	usbi_dbg(DEVICE_CTX(dev), "fd %d", fd);
 
 	ucd.ucd_config_index = USB_CURRENT_CONFIG_INDEX;
 
 	if ((ioctl(fd, USB_GET_CONFIG_DESC, &ucd)) < 0)
 		return _errno_to_libusb(errno);
 
-	usbi_dbg("active bLength %d", ucd.ucd_desc.bLength);
+	usbi_dbg(DEVICE_CTX(dev), "active bLength %d", ucd.ucd_desc.bLength);
 
 	len = UGETW(ucd.ucd_desc.wTotalLength);
 	buf = malloc((size_t)len);
@@ -490,7 +490,7 @@
 	ufd.ufd_size = len;
 	ufd.ufd_data = buf;
 
-	usbi_dbg("index %d, len %d", ufd.ufd_config_index, len);
+	usbi_dbg(DEVICE_CTX(dev), "index %d, len %d", ufd.ufd_config_index, len);
 
 	if ((ioctl(fd, USB_GET_FULL_DESC, &ufd)) < 0) {
 		free(buf);
@@ -516,7 +516,7 @@
 	dpriv = usbi_get_device_priv(transfer->dev_handle->dev);
 	setup = (struct libusb_control_setup *)transfer->buffer;
 
-	usbi_dbg("type %d request %d value %d index %d length %d timeout %d",
+	usbi_dbg(ITRANSFER_CTX(itransfer), "type %d request %d value %d index %d length %d timeout %d",
 	    setup->bmRequestType, setup->bRequest,
 	    libusb_le16_to_cpu(setup->wValue),
 	    libusb_le16_to_cpu(setup->wIndex),
@@ -541,7 +541,7 @@
 
 	itransfer->transferred = req.ucr_actlen;
 
-	usbi_dbg("transferred %d", itransfer->transferred);
+	usbi_dbg(ITRANSFER_CTX(itransfer), "transferred %d", itransfer->transferred);
 
 	return (0);
 }
@@ -561,7 +561,7 @@
 	endpt = UE_GET_ADDR(transfer->endpoint);
 	mode = IS_XFERIN(transfer) ? O_RDONLY : O_WRONLY;
 
-	usbi_dbg("endpoint %d mode %d", endpt, mode);
+	usbi_dbg(TRANSFER_CTX(transfer), "endpoint %d mode %d", endpt, mode);
 
 	if (hpriv->endpoints[endpt] < 0) {
 		/* Pick the right node given the control one */
diff --git a/libusb/os/openbsd_usb.c b/libusb/os/openbsd_usb.c
index e05610e..9a5c604 100644
--- a/libusb/os/openbsd_usb.c
+++ b/libusb/os/openbsd_usb.c
@@ -129,7 +129,7 @@
 	char *udevname;
 	int fd, addr, i, j;
 
-	usbi_dbg(" ");
+	usbi_dbg(ctx, " ");
 
 	for (i = 0; i < 8; i++) {
 		snprintf(busnode, sizeof(busnode), USBDEV "%d", i);
@@ -238,7 +238,7 @@
 			return _errno_to_libusb(errno);
 		dpriv->fd = fd;
 
-		usbi_dbg("open %s: fd %d", devnode, dpriv->fd);
+		usbi_dbg(HANDLE_CTX(handle), "open %s: fd %d", devnode, dpriv->fd);
 	}
 
 	return (LIBUSB_SUCCESS);
@@ -250,7 +250,7 @@
 	struct device_priv *dpriv = usbi_get_device_priv(handle->dev);
 
 	if (dpriv->devname) {
-		usbi_dbg("close: fd %d", dpriv->fd);
+		usbi_dbg(HANDLE_CTX(handle), "close: fd %d", dpriv->fd);
 
 		close(dpriv->fd);
 		dpriv->fd = -1;
@@ -265,7 +265,7 @@
 
 	len = MIN(len, (size_t)UGETW(dpriv->cdesc->wTotalLength));
 
-	usbi_dbg("len %zu", len);
+	usbi_dbg(DEVICE_CTX(dev), "len %zu", len);
 
 	memcpy(buf, dpriv->cdesc, len);
 
@@ -288,7 +288,7 @@
 	udf.udf_size = len;
 	udf.udf_data = buf;
 
-	usbi_dbg("index %d, len %zu", udf.udf_config_index, len);
+	usbi_dbg(DEVICE_CTX(dev), "index %d, len %zu", udf.udf_config_index, len);
 
 	if (ioctl(fd, USB_DEVICE_GET_FDESC, &udf) < 0) {
 		err = errno;
@@ -307,7 +307,7 @@
 
 	*config = dpriv->cdesc->bConfigurationValue;
 
-	usbi_dbg("bConfigurationValue %u", *config);
+	usbi_dbg(HANDLE_CTX(handle), "bConfigurationValue %u", *config);
 
 	return (LIBUSB_SUCCESS);
 }
@@ -320,7 +320,7 @@
 	if (dpriv->devname == NULL)
 		return (LIBUSB_ERROR_NOT_SUPPORTED);
 
-	usbi_dbg("bConfigurationValue %d", config);
+	usbi_dbg(HANDLE_CTX(handle), "bConfigurationValue %d", config);
 
 	if (ioctl(dpriv->fd, USB_SET_CONFIG, &config) < 0)
 		return _errno_to_libusb(errno);
@@ -367,7 +367,7 @@
 	if (dpriv->devname == NULL)
 		return (LIBUSB_ERROR_NOT_SUPPORTED);
 
-	usbi_dbg("iface %u, setting %u", iface, altsetting);
+	usbi_dbg(HANDLE_CTX(handle), "iface %u, setting %u", iface, altsetting);
 
 	memset(&intf, 0, sizeof(intf));
 
@@ -389,7 +389,7 @@
 	if ((fd = _bus_open(handle->dev->bus_number)) < 0)
 		return _errno_to_libusb(errno);
 
-	usbi_dbg(" ");
+	usbi_dbg(HANDLE_CTX(handle), " ");
 
 	req.ucr_addr = handle->dev->device_address;
 	req.ucr_request.bmRequestType = UT_WRITE_ENDPOINT;
@@ -413,7 +413,7 @@
 {
 	struct device_priv *dpriv = usbi_get_device_priv(dev);
 
-	usbi_dbg(" ");
+	usbi_dbg(DEVICE_CTX(dev), " ");
 
 	free(dpriv->cdesc);
 	free(dpriv->devname);
@@ -425,7 +425,7 @@
 	struct libusb_transfer *transfer;
 	int err = 0;
 
-	usbi_dbg(" ");
+	usbi_dbg(ITRANSFER_CTX(itransfer), " ");
 
 	transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
 
@@ -468,7 +468,7 @@
 {
 	UNUSED(itransfer);
 
-	usbi_dbg(" ");
+	usbi_dbg(ITRANSFER_CTX(itransfer), " ");
 
 	return (LIBUSB_ERROR_NOT_SUPPORTED);
 }
@@ -482,7 +482,7 @@
 int
 _errno_to_libusb(int err)
 {
-	usbi_dbg("error: %s (%d)", strerror(err), err);
+	usbi_dbg(NULL, "error: %s (%d)", strerror(err), err);
 
 	switch (err) {
 	case EIO:
@@ -512,7 +512,7 @@
 	if ((fd = _bus_open(dev->bus_number)) < 0)
 		return _errno_to_libusb(errno);
 
-	usbi_dbg("fd %d, addr %d", fd, dev->device_address);
+	usbi_dbg(DEVICE_CTX(dev), "fd %d, addr %d", fd, dev->device_address);
 
 	udc.udc_bus = dev->bus_number;
 	udc.udc_addr = dev->device_address;
@@ -523,7 +523,7 @@
 		return _errno_to_libusb(errno);
 	}
 
-	usbi_dbg("active bLength %d", udc.udc_desc.bLength);
+	usbi_dbg(DEVICE_CTX(dev), "active bLength %d", udc.udc_desc.bLength);
 
 	len = UGETW(udc.udc_desc.wTotalLength);
 	buf = malloc((size_t)len);
@@ -536,7 +536,7 @@
 	udf.udf_size = len;
 	udf.udf_data = buf;
 
-	usbi_dbg("index %d, len %d", udf.udf_config_index, len);
+	usbi_dbg(DEVICE_CTX(dev), "index %d, len %d", udf.udf_config_index, len);
 
 	if (ioctl(fd, USB_DEVICE_GET_FDESC, &udf) < 0) {
 		err = errno;
@@ -565,7 +565,7 @@
 	dpriv = usbi_get_device_priv(transfer->dev_handle->dev);
 	setup = (struct libusb_control_setup *)transfer->buffer;
 
-	usbi_dbg("type %x request %x value %x index %d length %d timeout %d",
+	usbi_dbg(ITRANSFER_CTX(itransfer), "type 0x%x request 0x%x value 0x%x index %d length %d timeout %d",
 	    setup->bmRequestType, setup->bRequest,
 	    libusb_le16_to_cpu(setup->wValue),
 	    libusb_le16_to_cpu(setup->wIndex),
@@ -610,7 +610,7 @@
 
 	itransfer->transferred = req.ucr_actlen;
 
-	usbi_dbg("transferred %d", itransfer->transferred);
+	usbi_dbg(ITRANSFER_CTX(itransfer), "transferred %d", itransfer->transferred);
 
 	return (0);
 }
@@ -630,7 +630,7 @@
 	endpt = UE_GET_ADDR(transfer->endpoint);
 	mode = IS_XFERIN(transfer) ? O_RDONLY : O_WRONLY;
 
-	usbi_dbg("endpoint %d mode %d", endpt, mode);
+	usbi_dbg(TRANSFER_CTX(transfer), "endpoint %d mode %d", endpt, mode);
 
 	if (hpriv->endpoints[endpt] < 0) {
 		/* Pick the right endpoint node */
diff --git a/libusb/os/sunos_usb.c b/libusb/os/sunos_usb.c
index 46866fb..28b167f 100644
--- a/libusb/os/sunos_usb.c
+++ b/libusb/os/sunos_usb.c
@@ -90,7 +90,7 @@
 		char *content = (char *)di_devlink_content(devlink);
 		char *start = strstr(content, "/devices/");
 		start += strlen("/devices");
-		usbi_dbg("%s", start);
+		usbi_dbg(NULL, "%s", start);
 
 		/* line content must have minor node */
 		if (start == NULL ||
@@ -101,7 +101,7 @@
 
 	p = di_devlink_path(devlink);
 	q = strrchr(p, '/');
-	usbi_dbg("%s", q);
+	usbi_dbg(NULL, "%s", q);
 
 	*(larg->linkpp) = strndup(p, strlen(p) - strlen(q));
 
@@ -118,7 +118,7 @@
 	*link_path = NULL;
 	larg.linkpp = link_path;
 	if ((hdl = di_devlink_init(NULL, 0)) == NULL) {
-		usbi_dbg("di_devlink_init failure");
+		usbi_dbg(NULL, "di_devlink_init failure");
 		return (-1);
 	}
 
@@ -131,7 +131,7 @@
 	(void) di_devlink_fini(&hdl);
 
 	if (*link_path == NULL) {
-		usbi_dbg("there is no devlink for this path");
+		usbi_dbg(NULL, "there is no devlink for this path");
 		return (-1);
 	}
 
@@ -167,13 +167,13 @@
 		return (-1);
 	}
 	end++;
-	usbi_dbg("unitaddr: %s", end);
+	usbi_dbg(DEVICE_CTX(dev), "unitaddr: %s", end);
 
 	nvlist_alloc(&nvlist, NV_UNIQUE_NAME_TYPE, KM_NOSLEEP);
 	nvlist_add_int32(nvlist, "port", dev->port_number);
 	//find the hub path
 	snprintf(path_arg, sizeof(path_arg), "/devices%s:hubd", hubpath);
-	usbi_dbg("ioctl hub path: %s", path_arg);
+	usbi_dbg(DEVICE_CTX(dev), "ioctl hub path: %s", path_arg);
 
 	fd = open(path_arg, O_RDONLY);
 	if (fd < 0) {
@@ -193,15 +193,15 @@
 	iocdata.c_nodename = (char *)"hub";
 	iocdata.c_unitaddr = end;
 	iocdata.cpyout_buf = &devctl_ap_state;
-	usbi_dbg("%p, %" PRIuPTR, iocdata.nvl_user, iocdata.nvl_usersz);
+	usbi_dbg(DEVICE_CTX(dev), "%p, %" PRIuPTR, iocdata.nvl_user, iocdata.nvl_usersz);
 
 	errno = 0;
 	if (ioctl(fd, DEVCTL_AP_GETSTATE, &iocdata) == -1) {
 		usbi_err(DEVICE_CTX(dev), "ioctl failed: fd %d, cmd %x, errno %d (%s)",
 			 fd, DEVCTL_AP_GETSTATE, errno, strerror(errno));
 	} else {
-		usbi_dbg("dev rstate: %d", devctl_ap_state.ap_rstate);
-		usbi_dbg("dev ostate: %d", devctl_ap_state.ap_ostate);
+		usbi_dbg(DEVICE_CTX(dev), "dev rstate: %d", devctl_ap_state.ap_rstate);
+		usbi_dbg(DEVICE_CTX(dev), "dev ostate: %d", devctl_ap_state.ap_ostate);
 	}
 
 	errno = 0;
@@ -227,7 +227,7 @@
 
 	UNUSED(interface);
 
-	usbi_dbg("%s", dpriv->ugenpath);
+	usbi_dbg(HANDLE_CTX(dev_handle), "%s", dpriv->ugenpath);
 
 	return (dpriv->ugenpath == NULL);
 }
@@ -236,7 +236,7 @@
  * Private functions
  */
 static int _errno_to_libusb(int);
-static int sunos_usb_get_status(int fd);
+static int sunos_usb_get_status(struct libusb_context *ctx, int fd);
 
 static string_list_t *
 sunos_new_string_list(void)
@@ -358,7 +358,7 @@
 
 	dpriv = usbi_get_device_priv(dev_handle->dev);
 	snprintf(path_arg, sizeof(path_arg), "\'\"%s\"\'", dpriv->phypath);
-	usbi_dbg("%s", path_arg);
+	usbi_dbg(HANDLE_CTX(dev_handle), "%s", path_arg);
 
 	list = sunos_new_string_list();
 	if (list == NULL)
@@ -418,7 +418,7 @@
 
 	dpriv = usbi_get_device_priv(dev_handle->dev);
 	snprintf(path_arg, sizeof(path_arg), "\'\"%s\"\'", dpriv->phypath);
-	usbi_dbg("%s", path_arg);
+	usbi_dbg(HANDLE_CTX(dev_handle), "%s", path_arg);
 
 	list = sunos_new_string_list();
 	if (list == NULL)
@@ -474,7 +474,7 @@
 	proplen = di_prop_lookup_bytes(DDI_DEV_T_ANY, node,
 	    "usb-raw-cfg-descriptors", &rdata);
 	if (proplen <= 0) {
-		usbi_dbg("can't find raw config descriptors");
+		usbi_dbg(DEVICE_CTX(dev), "can't find raw config descriptors");
 
 		return (LIBUSB_ERROR_IO);
 	}
@@ -501,7 +501,7 @@
 		snprintf(match_str, sizeof(match_str), "^usb/%x.%x",
 		    dev->device_descriptor.idVendor,
 		    dev->device_descriptor.idProduct);
-		usbi_dbg("match is %s", match_str);
+		usbi_dbg(DEVICE_CTX(dev), "match is %s", match_str);
 		sunos_physpath_to_devlink(dpriv->phypath, match_str,  &dpriv->ugenpath);
 		di_devfs_path_free(phypath);
 
@@ -514,7 +514,7 @@
 	/* address */
 	n = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "assigned-address", &addr);
 	if (n != 1 || *addr == 0) {
-		usbi_dbg("can't get address");
+		usbi_dbg(DEVICE_CTX(dev), "can't get address");
 	} else {
 		dev->device_address = *addr;
 	}
@@ -530,7 +530,7 @@
 		dev->speed = LIBUSB_SPEED_SUPER;
 	}
 
-	usbi_dbg("vid=%x pid=%x, path=%s, bus_nmber=0x%x, port_number=%d, speed=%d",
+	usbi_dbg(DEVICE_CTX(dev), "vid=%x pid=%x, path=%s, bus_nmber=0x%x, port_number=%d, speed=%d",
 	    dev->device_descriptor.idVendor, dev->device_descriptor.idProduct,
 	    dpriv->phypath, dev->bus_number, dev->port_number, dev->speed);
 
@@ -571,7 +571,7 @@
 	dn = myself;
 	/* find the root hub */
 	while (di_prop_lookup_ints(DDI_DEV_T_ANY, dn, "root-hub", &j) != 0) {
-		usbi_dbg("find_root_hub:%s", di_devfs_path(dn));
+		usbi_dbg(NULL, "find_root_hub:%s", di_devfs_path(dn));
 		n = di_prop_lookup_ints(DDI_DEV_T_ANY, dn,
 				"assigned-address", &addr_prop);
 		session_id |= ((addr_prop[0] & 0xff) << i++ * 8);
@@ -586,13 +586,13 @@
 	session_id |= (bdf << i * 8);
 	bus_number = (PCI_REG_DEV_G(reg) << 3) | PCI_REG_FUNC_G(reg);
 
-	usbi_dbg("device bus address=%s:%x, name:%s",
+	usbi_dbg(NULL, "device bus address=%s:%x, name:%s",
 	    di_bus_addr(myself), bus_number, di_node_name(dn));
-	usbi_dbg("session id org:%" PRIx64, session_id);
+	usbi_dbg(NULL, "session id org:%" PRIx64, session_id);
 
 	/* dn is the usb device */
 	for (dn = di_child_node(myself); dn != DI_NODE_NIL; dn = di_sibling_node(dn)) {
-		usbi_dbg("device path:%s", di_devfs_path(dn));
+		usbi_dbg(NULL, "device path:%s", di_devfs_path(dn));
 		/* skip hub devices, because its driver can not been unload */
 		if (di_prop_lookup_ints(DDI_DEV_T_ANY, dn, "usb-port-count", &addr_prop) != -1)
 			continue;
@@ -600,18 +600,18 @@
 		n = di_prop_lookup_ints(DDI_DEV_T_ANY, dn,
 		    "assigned-address", &addr_prop);
 		if ((n != 1) || (addr_prop[0] == 0)) {
-			usbi_dbg("cannot get valid usb_addr");
+			usbi_dbg(NULL, "cannot get valid usb_addr");
 			continue;
 		}
 
 		sid = (session_id << 8) | (addr_prop[0] & 0xff) ;
-		usbi_dbg("session id %" PRIX64, sid);
+		usbi_dbg(NULL, "session id %" PRIX64, sid);
 
 		dev = usbi_get_device_by_session_id(nargs->ctx, sid);
 		if (dev == NULL) {
 			dev = usbi_alloc_device(nargs->ctx, sid);
 			if (dev == NULL) {
-				usbi_dbg("can't alloc device");
+				usbi_dbg(NULL, "can't alloc device");
 				continue;
 			}
 			devpriv = usbi_get_device_priv(dev);
@@ -619,21 +619,21 @@
 
 			if (sunos_fill_in_dev_info(dn, dev) != LIBUSB_SUCCESS) {
 				libusb_unref_device(dev);
-				usbi_dbg("get information fail");
+				usbi_dbg(NULL, "get information fail");
 				continue;
 			}
 			if (usbi_sanitize_device(dev) < 0) {
 				libusb_unref_device(dev);
-				usbi_dbg("sanatize failed: ");
+				usbi_dbg(NULL, "sanatize failed: ");
 				return (DI_WALK_TERMINATE);
 			}
 		} else {
 			devpriv = usbi_get_device_priv(dev);
-			usbi_dbg("Dev %s exists", devpriv->ugenpath);
+			usbi_dbg(NULL, "Dev %s exists", devpriv->ugenpath);
 		}
 
 		if (discovered_devs_append(*(nargs->discdevs), dev) == NULL) {
-			usbi_dbg("cannot append device");
+			usbi_dbg(NULL, "cannot append device");
 		}
 
 		/*
@@ -642,7 +642,7 @@
 		 */
 		libusb_unref_device(dev);
 
-		usbi_dbg("Device %s %s id=0x%" PRIx64 ", devcount:%" PRIuPTR
+		usbi_dbg(NULL, "Device %s %s id=0x%" PRIx64 ", devcount:%" PRIuPTR
 		    ", bdf=%" PRIx64,
 		    devpriv->ugenpath, di_devfs_path(dn), (uint64_t)sid,
 		    (*nargs->discdevs)->len, bdf);
@@ -690,13 +690,13 @@
 	args.discdevs = discdevs;
 	args.last_ugenpath = NULL;
 	if ((root_node = di_init("/", DINFOCPYALL)) == DI_NODE_NIL) {
-		usbi_dbg("di_int() failed: errno %d (%s)", errno, strerror(errno));
+		usbi_dbg(ctx, "di_int() failed: errno %d (%s)", errno, strerror(errno));
 		return (LIBUSB_ERROR_IO);
 	}
 
 	if ((devlink_hdl = di_devlink_init(NULL, 0)) == NULL) {
 		di_fini(root_node);
-		usbi_dbg("di_devlink_init() failed: errno %d (%s)", errno, strerror(errno));
+		usbi_dbg(ctx, "di_devlink_init() failed: errno %d (%s)", errno, strerror(errno));
 
 		return (LIBUSB_ERROR_IO);
 	}
@@ -705,7 +705,7 @@
 	/* walk each node to find USB devices */
 	if (di_walk_node(root_node, DI_WALK_SIBFIRST, &args,
 	    sunos_walk_minor_node_link) == -1) {
-		usbi_dbg("di_walk_node() failed: errno %d (%s)", errno, strerror(errno));
+		usbi_dbg(ctx, "di_walk_node() failed: errno %d (%s)", errno, strerror(errno));
 		di_fini(root_node);
 
 		return (LIBUSB_ERROR_IO);
@@ -714,7 +714,7 @@
 	di_fini(root_node);
 	di_devlink_fini(&devlink_hdl);
 
-	usbi_dbg("%zu devices", (*discdevs)->len);
+	usbi_dbg(ctx, "%zu devices", (*discdevs)->len);
 
 	return ((*discdevs)->len);
 }
@@ -729,7 +729,7 @@
 	}
 	snprintf(filename, PATH_MAX, "%s/cntrl0", dpriv->ugenpath);
 
-	usbi_dbg("opening %s", filename);
+	usbi_dbg(NULL, "opening %s", filename);
 	hpriv->eps[0].datafd = open(filename, O_RDWR);
 	if (hpriv->eps[0].datafd < 0) {
 		return(_errno_to_libusb(errno));
@@ -836,20 +836,20 @@
 	uint8_t	ep_index;
 	sunos_dev_handle_priv_t *hpriv;
 
-	usbi_dbg("open ep 0x%02x", ep_addr);
+	usbi_dbg(HANDLE_CTX(hdl), "open ep 0x%02x", ep_addr);
 	hpriv = usbi_get_device_handle_priv(hdl);
 	ep_index = sunos_usb_ep_index(ep_addr);
 	/* ep already opened */
 	if ((hpriv->eps[ep_index].datafd > 0) &&
 	    (hpriv->eps[ep_index].statfd > 0)) {
-		usbi_dbg("ep 0x%02x already opened, return success",
+		usbi_dbg(HANDLE_CTX(hdl), "ep 0x%02x already opened, return success",
 			ep_addr);
 
 		return (0);
 	}
 
 	if (sunos_find_interface(hdl, ep_addr, &ifc) < 0) {
-		usbi_dbg("can't find interface for endpoint 0x%02x",
+		usbi_dbg(HANDLE_CTX(hdl), "can't find interface for endpoint 0x%02x",
 		    ep_addr);
 
 		return (EACCES);
@@ -896,7 +896,7 @@
 	}
 	/* Open the xfer endpoint first */
 	if ((fd = open(filename, mode)) == -1) {
-		usbi_dbg("can't open %s: errno %d (%s)", filename, errno,
+		usbi_dbg(HANDLE_CTX(hdl), "can't open %s: errno %d (%s)", filename, errno,
 		    strerror(errno));
 
 		return (errno);
@@ -917,7 +917,7 @@
 
 		/* Open the status endpoint with RDWR */
 		if ((fdstat = open(statfilename, O_RDWR)) == -1) {
-			usbi_dbg("can't open %s RDWR: errno %d (%s)",
+			usbi_dbg(HANDLE_CTX(hdl), "can't open %s RDWR: errno %d (%s)",
 				statfilename, errno, strerror(errno));
 
 			return (errno);
@@ -925,7 +925,7 @@
 			count = write(fdstat, &control, sizeof(control));
 			if (count != 1) {
 				/* this should have worked */
-				usbi_dbg("can't write to %s: errno %d (%s)",
+				usbi_dbg(HANDLE_CTX(hdl), "can't write to %s: errno %d (%s)",
 					statfilename, errno, strerror(errno));
 				(void) close(fdstat);
 
@@ -934,7 +934,7 @@
 		}
 	} else {
 		if ((fdstat = open(statfilename, O_RDONLY)) == -1) {
-			usbi_dbg("can't open %s: errno %d (%s)", statfilename, errno,
+			usbi_dbg(HANDLE_CTX(hdl), "can't open %s: errno %d (%s)", statfilename, errno,
 				strerror(errno));
 
 			return (errno);
@@ -943,7 +943,7 @@
 
 	/* Re-open the xfer endpoint */
 	if ((fd = open(filename, mode)) == -1) {
-		usbi_dbg("can't open %s: errno %d (%s)", filename, errno,
+		usbi_dbg(HANDLE_CTX(hdl), "can't open %s: errno %d (%s)", filename, errno,
 			strerror(errno));
 		(void) close(fdstat);
 
@@ -952,7 +952,7 @@
 
 	hpriv->eps[ep_index].datafd = fd;
 	hpriv->eps[ep_index].statfd = fdstat;
-	usbi_dbg("ep=0x%02x datafd=%d, statfd=%d", ep_addr, fd, fdstat);
+	usbi_dbg(HANDLE_CTX(hdl), "ep=0x%02x datafd=%d, statfd=%d", ep_addr, fd, fdstat);
 
 	return (0);
 }
@@ -981,7 +981,7 @@
 	}
 
 	if ((ret = sunos_usb_open_ep0(hpriv, dpriv)) != LIBUSB_SUCCESS) {
-		usbi_dbg("fail: %d", ret);
+		usbi_dbg(HANDLE_CTX(handle), "fail: %d", ret);
 		return (ret);
 	}
 
@@ -993,7 +993,7 @@
 {
 	sunos_dev_handle_priv_t *hpriv;
 
-	usbi_dbg(" ");
+	usbi_dbg(HANDLE_CTX(handle), " ");
 
 	hpriv = usbi_get_device_handle_priv(handle);
 
@@ -1016,14 +1016,14 @@
 	 * has ever been changed through setCfg.
 	 */
 	if ((node = di_init(dpriv->phypath, DINFOCPYALL)) == DI_NODE_NIL) {
-		usbi_dbg("di_int() failed: errno %d (%s)", errno,
+		usbi_dbg(DEVICE_CTX(dev), "di_int() failed: errno %d (%s)", errno,
 			strerror(errno));
 		return (LIBUSB_ERROR_IO);
 	}
 	proplen = di_prop_lookup_bytes(DDI_DEV_T_ANY, node,
 	    "usb-raw-cfg-descriptors", &rdata);
 	if (proplen <= 0) {
-		usbi_dbg("can't find raw config descriptors");
+		usbi_dbg(DEVICE_CTX(dev), "can't find raw config descriptors");
 
 		return (LIBUSB_ERROR_IO);
 	}
@@ -1040,7 +1040,7 @@
 	cfg = (struct libusb_config_descriptor *)dpriv->raw_cfgdescr;
 	len = MIN(len, libusb_le16_to_cpu(cfg->wTotalLength));
 	memcpy(buf, dpriv->raw_cfgdescr, len);
-	usbi_dbg("path:%s len %zu", dpriv->phypath, len);
+	usbi_dbg(DEVICE_CTX(dev), "path:%s len %zu", dpriv->phypath, len);
 
 	return (len);
 }
@@ -1061,7 +1061,7 @@
 
 	*config = dpriv->cfgvalue;
 
-	usbi_dbg("bConfigurationValue %u", *config);
+	usbi_dbg(HANDLE_CTX(handle), "bConfigurationValue %u", *config);
 
 	return (LIBUSB_SUCCESS);
 }
@@ -1072,7 +1072,7 @@
 	sunos_dev_priv_t *dpriv = usbi_get_device_priv(handle->dev);
 	sunos_dev_handle_priv_t *hpriv;
 
-	usbi_dbg("bConfigurationValue %d", config);
+	usbi_dbg(HANDLE_CTX(handle), "bConfigurationValue %d", config);
 	hpriv = usbi_get_device_handle_priv(handle);
 
 	if (dpriv->ugenpath == NULL)
@@ -1092,7 +1092,7 @@
 {
 	UNUSED(handle);
 
-	usbi_dbg("iface %u", iface);
+	usbi_dbg(HANDLE_CTX(handle), "iface %u", iface);
 
 	return (LIBUSB_SUCCESS);
 }
@@ -1102,7 +1102,7 @@
 {
 	sunos_dev_handle_priv_t *hpriv = usbi_get_device_handle_priv(handle);
 
-	usbi_dbg("iface %u", iface);
+	usbi_dbg(HANDLE_CTX(handle), "iface %u", iface);
 
 	/* XXX: can we release it? */
 	hpriv->altsetting[iface] = 0;
@@ -1117,7 +1117,7 @@
 	sunos_dev_priv_t *dpriv = usbi_get_device_priv(handle->dev);
 	sunos_dev_handle_priv_t *hpriv = usbi_get_device_handle_priv(handle);
 
-	usbi_dbg("iface %u, setting %u", iface, altsetting);
+	usbi_dbg(HANDLE_CTX(handle), "iface %u, setting %u", iface, altsetting);
 
 	if (dpriv->ugenpath == NULL)
 		return (LIBUSB_ERROR_NOT_FOUND);
@@ -1169,7 +1169,7 @@
 
 		ret = aio_error(aiocb);
 		if (ret != 0) {
-			xfer->status = sunos_usb_get_status(hpriv->eps[ep].statfd);
+			xfer->status = sunos_usb_get_status(TRANSFER_CTX(xfer), hpriv->eps[ep].statfd);
 		} else {
 			xfer->actual_length =
 			    LIBUSB_TRANSFER_TO_USBI_TRANSFER(xfer)->transferred =
@@ -1178,7 +1178,7 @@
 
 		usb_dump_data(xfer->buffer, xfer->actual_length);
 
-		usbi_dbg("ret=%d, len=%d, actual_len=%d", ret, xfer->length,
+		usbi_dbg(TRANSFER_CTX(xfer), "ret=%d, len=%d, actual_len=%d", ret, xfer->length,
 		    xfer->actual_length);
 
 		/* async notification */
@@ -1195,7 +1195,7 @@
 	uint8_t ep;
 	struct sunos_transfer_priv *tpriv;
 
-	usbi_dbg(" ");
+	usbi_dbg(TRANSFER_CTX(transfer), " ");
 
 	tpriv = usbi_get_transfer_priv(LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer));
 	hpriv = usbi_get_device_handle_priv(transfer->dev_handle);
@@ -1225,12 +1225,12 @@
 
 /* return the number of bytes read/written */
 static ssize_t
-usb_do_io(int fd, int stat_fd, void *data, size_t size, int flag, int *status)
+usb_do_io(struct libusb_context *ctx, int fd, int stat_fd, void *data, size_t size, int flag, int *status)
 {
 	int error;
 	ssize_t ret = -1;
 
-	usbi_dbg("usb_do_io(): datafd=%d statfd=%d size=0x%zx flag=%s",
+	usbi_dbg(ctx, "usb_do_io(): datafd=%d statfd=%d size=0x%zx flag=%s",
 	    fd, stat_fd, size, flag? "WRITE":"READ");
 
 	switch (flag) {
@@ -1246,17 +1246,17 @@
 		break;
 	}
 
-	usbi_dbg("usb_do_io(): amount=%zd", ret);
+	usbi_dbg(ctx, "usb_do_io(): amount=%zd", ret);
 
 	if (ret < 0) {
 		int save_errno = errno;
 
-		usbi_dbg("TID=%x io %s errno %d (%s)", pthread_self(),
+		usbi_dbg(ctx, "TID=%x io %s errno %d (%s)", pthread_self(),
 		    flag?"WRITE":"READ", errno, strerror(errno));
 
 		/* sunos_usb_get_status will do a read and overwrite errno */
-		error = sunos_usb_get_status(stat_fd);
-		usbi_dbg("io status=%d errno %d (%s)", error,
+		error = sunos_usb_get_status(ctx, stat_fd);
+		usbi_dbg(ctx, "io status=%d errno %d (%s)", error,
 			save_errno, strerror(save_errno));
 
 		if (status) {
@@ -1286,26 +1286,26 @@
 	wLength = transfer->length - LIBUSB_CONTROL_SETUP_SIZE;
 
 	if (hpriv->eps[0].datafd == -1) {
-		usbi_dbg("ep0 not opened");
+		usbi_dbg(TRANSFER_CTX(transfer), "ep0 not opened");
 
 		return (LIBUSB_ERROR_NOT_FOUND);
 	}
 
 	if ((data[0] & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_IN) {
-		usbi_dbg("IN request");
-		ret = usb_do_io(hpriv->eps[0].datafd,
+		usbi_dbg(TRANSFER_CTX(transfer), "IN request");
+		ret = usb_do_io(TRANSFER_CTX(transfer), hpriv->eps[0].datafd,
 		    hpriv->eps[0].statfd, data, LIBUSB_CONTROL_SETUP_SIZE,
 		    WRITE, &status);
 	} else {
-		usbi_dbg("OUT request");
-		ret = usb_do_io(hpriv->eps[0].datafd, hpriv->eps[0].statfd,
+		usbi_dbg(TRANSFER_CTX(transfer), "OUT request");
+		ret = usb_do_io(TRANSFER_CTX(transfer), hpriv->eps[0].datafd, hpriv->eps[0].statfd,
 		    transfer->buffer, transfer->length, WRITE,
 		    (int *)&transfer->status);
 	}
 
 	setup_ret = ret;
 	if (ret < (ssize_t)LIBUSB_CONTROL_SETUP_SIZE) {
-		usbi_dbg("error sending control msg: %zd", ret);
+		usbi_dbg(TRANSFER_CTX(transfer), "error sending control msg: %zd", ret);
 
 		return (LIBUSB_ERROR_IO);
 	}
@@ -1315,8 +1315,8 @@
 	/* Read the remaining bytes for IN request */
 	if ((wLength) && ((data[0] & LIBUSB_ENDPOINT_DIR_MASK) ==
 	    LIBUSB_ENDPOINT_IN)) {
-		usbi_dbg("DATA: %d", transfer->length - (int)setup_ret);
-		ret = usb_do_io(hpriv->eps[0].datafd,
+		usbi_dbg(TRANSFER_CTX(transfer), "DATA: %d", transfer->length - (int)setup_ret);
+		ret = usb_do_io(TRANSFER_CTX(transfer), hpriv->eps[0].datafd,
 			hpriv->eps[0].statfd,
 			transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE,
 			wLength, READ, (int *)&transfer->status);
@@ -1325,7 +1325,7 @@
 	if (ret >= 0) {
 		LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer)->transferred = ret;
 	}
-	usbi_dbg("Done: ctrl data bytes %zd", ret);
+	usbi_dbg(TRANSFER_CTX(transfer), "Done: ctrl data bytes %zd", ret);
 
 	/**
 	 * Sync transfer handling.
@@ -1345,13 +1345,13 @@
 {
 	int ret;
 
-	usbi_dbg("endpoint=0x%02x", endpoint);
+	usbi_dbg(HANDLE_CTX(handle), "endpoint=0x%02x", endpoint);
 
 	ret = libusb_control_transfer(handle, LIBUSB_ENDPOINT_OUT |
 	    LIBUSB_RECIPIENT_ENDPOINT | LIBUSB_REQUEST_TYPE_STANDARD,
 	    LIBUSB_REQUEST_CLEAR_FEATURE, 0, endpoint, NULL, 0, 1000);
 
-	usbi_dbg("ret=%d", ret);
+	usbi_dbg(HANDLE_CTX(handle), "ret=%d", ret);
 
 	return (ret);
 }
@@ -1361,7 +1361,7 @@
 {
 	sunos_dev_priv_t *dpriv = usbi_get_device_priv(dev);
 
-	usbi_dbg("destroy everything");
+	usbi_dbg(DEVICE_CTX(dev), "destroy everything");
 	free(dpriv->raw_cfgdescr);
 	free(dpriv->ugenpath);
 	free(dpriv->phypath);
@@ -1387,7 +1387,7 @@
 	switch (transfer->type) {
 	case LIBUSB_TRANSFER_TYPE_CONTROL:
 		/* sync transfer */
-		usbi_dbg("CTRL transfer: %d", transfer->length);
+		usbi_dbg(ITRANSFER_CTX(itransfer), "CTRL transfer: %d", transfer->length);
 		err = solaris_submit_ctrl_on_default(transfer);
 		break;
 
@@ -1395,9 +1395,9 @@
 		/* fallthru */
 	case LIBUSB_TRANSFER_TYPE_INTERRUPT:
 		if (transfer->type == LIBUSB_TRANSFER_TYPE_BULK)
-			usbi_dbg("BULK transfer: %d", transfer->length);
+			usbi_dbg(ITRANSFER_CTX(itransfer), "BULK transfer: %d", transfer->length);
 		else
-			usbi_dbg("INTR transfer: %d", transfer->length);
+			usbi_dbg(ITRANSFER_CTX(itransfer), "INTR transfer: %d", transfer->length);
 		err = sunos_do_async_io(transfer);
 		break;
 
@@ -1407,9 +1407,9 @@
 		/* fallthru */
 	case LIBUSB_TRANSFER_TYPE_BULK_STREAM:
 		if (transfer->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS)
-			usbi_dbg("ISOC transfer: %d", transfer->length);
+			usbi_dbg(ITRANSFER_CTX(itransfer), "ISOC transfer: %d", transfer->length);
 		else
-			usbi_dbg("BULK STREAM transfer: %d", transfer->length);
+			usbi_dbg(ITRANSFER_CTX(itransfer), "BULK STREAM transfer: %d", transfer->length);
 		err = LIBUSB_ERROR_NOT_SUPPORTED;
 		break;
 	}
@@ -1435,7 +1435,7 @@
 
 	ret = aio_cancel(hpriv->eps[ep].datafd, aiocb);
 
-	usbi_dbg("aio->fd=%d fd=%d ret = %d, %s", aiocb->aio_fildes,
+	usbi_dbg(ITRANSFER_CTX(itransfer), "aio->fd=%d fd=%d ret = %d, %s", aiocb->aio_fildes,
 	    hpriv->eps[ep].datafd, ret, (ret == AIO_CANCELED)?
 	    strerror(0):strerror(errno));
 
@@ -1461,7 +1461,7 @@
 int
 _errno_to_libusb(int err)
 {
-	usbi_dbg("error: %s (%d)", strerror(err), err);
+	usbi_dbg(NULL, "error: %s (%d)", strerror(err), err);
 
 	switch (err) {
 	case EIO:
@@ -1486,96 +1486,96 @@
  * Returns: ugen's last cmd status
  */
 static int
-sunos_usb_get_status(int fd)
+sunos_usb_get_status(struct libusb_context *ctx, int fd)
 {
 	int status;
 	ssize_t ret;
 
-	usbi_dbg("sunos_usb_get_status(): fd=%d", fd);
+	usbi_dbg(ctx, "sunos_usb_get_status(): fd=%d", fd);
 
 	ret = read(fd, &status, sizeof(status));
 	if (ret == sizeof(status)) {
 		switch (status) {
 		case USB_LC_STAT_NOERROR:
-			usbi_dbg("No Error");
+			usbi_dbg(ctx, "No Error");
 			break;
 		case USB_LC_STAT_CRC:
-			usbi_dbg("CRC Timeout Detected\n");
+			usbi_dbg(ctx, "CRC Timeout Detected\n");
 			break;
 		case USB_LC_STAT_BITSTUFFING:
-			usbi_dbg("Bit Stuffing Violation\n");
+			usbi_dbg(ctx, "Bit Stuffing Violation\n");
 			break;
 		case USB_LC_STAT_DATA_TOGGLE_MM:
-			usbi_dbg("Data Toggle Mismatch\n");
+			usbi_dbg(ctx, "Data Toggle Mismatch\n");
 			break;
 		case USB_LC_STAT_STALL:
-			usbi_dbg("End Point Stalled\n");
+			usbi_dbg(ctx, "End Point Stalled\n");
 			break;
 		case USB_LC_STAT_DEV_NOT_RESP:
-			usbi_dbg("Device is Not Responding\n");
+			usbi_dbg(ctx, "Device is Not Responding\n");
 			break;
 		case USB_LC_STAT_PID_CHECKFAILURE:
-			usbi_dbg("PID Check Failure\n");
+			usbi_dbg(ctx, "PID Check Failure\n");
 			break;
 		case USB_LC_STAT_UNEXP_PID:
-			usbi_dbg("Unexpected PID\n");
+			usbi_dbg(ctx, "Unexpected PID\n");
 			break;
 		case USB_LC_STAT_DATA_OVERRUN:
-			usbi_dbg("Data Exceeded Size\n");
+			usbi_dbg(ctx, "Data Exceeded Size\n");
 			break;
 		case USB_LC_STAT_DATA_UNDERRUN:
-			usbi_dbg("Less data received\n");
+			usbi_dbg(ctx, "Less data received\n");
 			break;
 		case USB_LC_STAT_BUFFER_OVERRUN:
-			usbi_dbg("Buffer Size Exceeded\n");
+			usbi_dbg(ctx, "Buffer Size Exceeded\n");
 			break;
 		case USB_LC_STAT_BUFFER_UNDERRUN:
-			usbi_dbg("Buffer Underrun\n");
+			usbi_dbg(ctx, "Buffer Underrun\n");
 			break;
 		case USB_LC_STAT_TIMEOUT:
-			usbi_dbg("Command Timed Out\n");
+			usbi_dbg(ctx, "Command Timed Out\n");
 			break;
 		case USB_LC_STAT_NOT_ACCESSED:
-			usbi_dbg("Not Accessed by h/w\n");
+			usbi_dbg(ctx, "Not Accessed by h/w\n");
 			break;
 		case USB_LC_STAT_UNSPECIFIED_ERR:
-			usbi_dbg("Unspecified Error\n");
+			usbi_dbg(ctx, "Unspecified Error\n");
 			break;
 		case USB_LC_STAT_NO_BANDWIDTH:
-			usbi_dbg("No Bandwidth\n");
+			usbi_dbg(ctx, "No Bandwidth\n");
 			break;
 		case USB_LC_STAT_HW_ERR:
-			usbi_dbg("Host Controller h/w Error\n");
+			usbi_dbg(ctx, "Host Controller h/w Error\n");
 			break;
 		case USB_LC_STAT_SUSPENDED:
-			usbi_dbg("Device was Suspended\n");
+			usbi_dbg(ctx, "Device was Suspended\n");
 			break;
 		case USB_LC_STAT_DISCONNECTED:
-			usbi_dbg("Device was Disconnected\n");
+			usbi_dbg(ctx, "Device was Disconnected\n");
 			break;
 		case USB_LC_STAT_INTR_BUF_FULL:
-			usbi_dbg("Interrupt buffer was full\n");
+			usbi_dbg(ctx, "Interrupt buffer was full\n");
 			break;
 		case USB_LC_STAT_INVALID_REQ:
-			usbi_dbg("Request was Invalid\n");
+			usbi_dbg(ctx, "Request was Invalid\n");
 			break;
 		case USB_LC_STAT_INTERRUPTED:
-			usbi_dbg("Request was Interrupted\n");
+			usbi_dbg(ctx, "Request was Interrupted\n");
 			break;
 		case USB_LC_STAT_NO_RESOURCES:
-			usbi_dbg("No resources available for "
+			usbi_dbg(ctx, "No resources available for "
 			    "request\n");
 			break;
 		case USB_LC_STAT_INTR_POLLING_FAILED:
-			usbi_dbg("Failed to Restart Poll");
+			usbi_dbg(ctx, "Failed to Restart Poll");
 			break;
 		default:
-			usbi_dbg("Error Not Determined %d\n",
+			usbi_dbg(ctx, "Error Not Determined %d\n",
 			    status);
 			break;
 		}
 	} else {
-		usbi_dbg("read stat error: %s",strerror(errno));
+		usbi_dbg(ctx, "read stat error: %s",strerror(errno));
 		status = -1;
 	}
 
diff --git a/libusb/os/windows_common.c b/libusb/os/windows_common.c
index 119ed49..24ac095 100644
--- a/libusb/os/windows_common.c
+++ b/libusb/os/windows_common.c
@@ -24,7 +24,6 @@
 
 #include <config.h>
 
-#include <process.h>
 #include <stdio.h>
 
 #include "libusbi.h"
@@ -153,7 +152,7 @@
 	// Create a mutex
 	usbi_mutex_init(&htab_mutex);
 
-	usbi_dbg("using %lu entries hash table", HTAB_SIZE);
+	usbi_dbg(ctx, "using %lu entries hash table", HTAB_SIZE);
 	htab_filled = 0;
 
 	// allocate memory and zero out.
@@ -222,7 +221,7 @@
 		if ((htab_table[idx].used == hval) && (strcmp(str, htab_table[idx].str) == 0))
 			goto out_unlock; // existing hash
 
-		usbi_dbg("hash collision ('%s' vs '%s')", str, htab_table[idx].str);
+		usbi_dbg(NULL, "hash collision ('%s' vs '%s')", str, htab_table[idx].str);
 
 		// Second hash function, as suggested in [Knuth]
 		hval2 = 1UL + hval % (HTAB_SIZE - 2);
@@ -284,7 +283,7 @@
 	case USBD_STATUS_DEVICE_GONE:
 		return LIBUSB_TRANSFER_NO_DEVICE;
 	default:
-		usbi_dbg("USBD_STATUS 0x%08lx translated to LIBUSB_TRANSFER_ERROR", ULONG_CAST(status));
+		usbi_dbg(NULL, "USBD_STATUS 0x%08lx translated to LIBUSB_TRANSFER_ERROR", ULONG_CAST(status));
 		return LIBUSB_TRANSFER_ERROR;
 	}
 }
@@ -294,15 +293,18 @@
  */
 void windows_force_sync_completion(struct usbi_transfer *itransfer, ULONG size)
 {
+	struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
+	struct windows_context_priv *priv = usbi_get_context_priv(TRANSFER_CTX(transfer));
 	struct windows_transfer_priv *transfer_priv = usbi_get_transfer_priv(itransfer);
 	OVERLAPPED *overlapped = &transfer_priv->overlapped;
 
-	usbi_dbg("transfer %p, length %lu", USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer), ULONG_CAST(size));
+	usbi_dbg(TRANSFER_CTX(transfer), "transfer %p, length %lu", transfer, ULONG_CAST(size));
 
 	overlapped->Internal = (ULONG_PTR)STATUS_SUCCESS;
 	overlapped->InternalHigh = (ULONG_PTR)size;
 
-	usbi_signal_transfer_completion(itransfer);
+	if (!PostQueuedCompletionStatus(priv->completion_port, (DWORD)size, (ULONG_PTR)transfer->dev_handle, overlapped))
+		usbi_err(TRANSFER_CTX(transfer), "failed to post I/O completion: %s", windows_error_str(0));
 }
 
 /* Windows version detection */
@@ -344,6 +346,8 @@
 	if ((vi.dwMajorVersion > 6) || ((vi.dwMajorVersion == 6) && (vi.dwMinorVersion >= 2))) {
 		// Starting with Windows 8.1 Preview, GetVersionEx() does no longer report the actual OS version
 		// See: http://msdn.microsoft.com/en-us/library/windows/desktop/dn302074.aspx
+		// And starting with Windows 10 Preview 2, Windows enforces the use of the application/supportedOS
+		// manifest in order for VerSetConditionMask() to report the ACTUAL OS major and minor...
 
 		major_equal = VerSetConditionMask(0, VER_MAJORVERSION, VER_EQUAL);
 		for (major = vi.dwMajorVersion; major <= 9; major++) {
@@ -379,6 +383,7 @@
 
 	ws = (vi.wProductType <= VER_NT_WORKSTATION);
 	version = vi.dwMajorVersion << 4 | vi.dwMinorVersion;
+
 	switch (version) {
 	case 0x50: winver = WINDOWS_2000;  w = "2000"; break;
 	case 0x51: winver = WINDOWS_XP;	   w = "XP";   break;
@@ -388,22 +393,30 @@
 	case 0x62: winver = WINDOWS_8;	   w = (ws ? "8" : "2012");	 break;
 	case 0x63: winver = WINDOWS_8_1;   w = (ws ? "8.1" : "2012_R2"); break;
 	case 0x64: // Early Windows 10 Insider Previews and Windows Server 2017 Technical Preview 1 used version 6.4
-	case 0xA0: winver = WINDOWS_10;	   w = (ws ? "10" : "2016");	 break;
+	case 0xA0: winver = WINDOWS_10;	   w = (ws ? "10" : "2016");
+		   if (vi.dwBuildNumber < 20000)
+			   break;
+		   // fallthrough
+	case 0xB0: winver = WINDOWS_11;	   w = (ws ? "11" : "2022");	 break;
 	default:
 		if (version < 0x50)
 			return WINDOWS_UNDEFINED;
-		winver = WINDOWS_11_OR_LATER;
-		w = "11 or later";
+		winver = WINDOWS_12_OR_LATER;
+		w = "12 or later";
 	}
 
+	// We cannot tell if we are on 8, 10, or 11 without "app manifest"
+	if (version == 0x62 && vi.dwBuildNumber == 9200)
+		w = "8 (or later)";
+
 	arch = is_x64() ? "64-bit" : "32-bit";
 
 	if (vi.wServicePackMinor)
-		usbi_dbg("Windows %s SP%u.%u %s", w, vi.wServicePackMajor, vi.wServicePackMinor, arch);
+		usbi_dbg(NULL, "Windows %s SP%u.%u %s", w, vi.wServicePackMajor, vi.wServicePackMinor, arch);
 	else if (vi.wServicePackMajor)
-		usbi_dbg("Windows %s SP%u %s", w, vi.wServicePackMajor, arch);
+		usbi_dbg(NULL, "Windows %s SP%u %s", w, vi.wServicePackMajor, arch);
 	else
-		usbi_dbg("Windows %s %s", w, arch);
+		usbi_dbg(NULL, "Windows %s %s", w, arch);
 
 	return winver;
 }
@@ -416,10 +429,14 @@
 	DWORD num_bytes;
 	ULONG_PTR completion_key;
 	OVERLAPPED *overlapped;
+	struct libusb_device_handle *dev_handle;
+	struct libusb_device_handle *opened_device_handle;
+	struct windows_device_handle_priv *handle_priv;
 	struct windows_transfer_priv *transfer_priv;
 	struct usbi_transfer *itransfer;
+	bool found;
 
-	usbi_dbg("I/O completion thread started");
+	usbi_dbg(ctx, "I/O completion thread started");
 
 	while (true) {
 		overlapped = NULL;
@@ -435,14 +452,48 @@
 			break;
 		}
 
-		transfer_priv = container_of(overlapped, struct windows_transfer_priv, overlapped);
+		// Find the transfer associated with the OVERLAPPED that just completed.
+		// If we cannot find a match, the I/O operation originated from outside of libusb
+		// (e.g. within libusbK) and we need to ignore it.
+		dev_handle = (struct libusb_device_handle *)completion_key;
+
+		found = false;
+		transfer_priv = NULL;
+
+		// Issue 912: lock opened device handles in context to search the current device handle
+		// to avoid accessing unallocated memory after device has been closed
+		usbi_mutex_lock(&ctx->open_devs_lock);
+		for_each_open_device(ctx, opened_device_handle) {
+			if (dev_handle == opened_device_handle) {
+				handle_priv = usbi_get_device_handle_priv(dev_handle);
+
+				usbi_mutex_lock(&dev_handle->lock);
+				list_for_each_entry(transfer_priv, &handle_priv->active_transfers, list, struct windows_transfer_priv) {
+					if (overlapped == &transfer_priv->overlapped) {
+						// This OVERLAPPED belongs to us, remove the transfer from the device handle's list
+						list_del(&transfer_priv->list);
+						found = true;
+						break;
+					}
+				}
+				usbi_mutex_unlock(&dev_handle->lock);
+			}
+		}
+		usbi_mutex_unlock(&ctx->open_devs_lock);
+
+		if (!found) {
+			usbi_dbg(ctx, "ignoring overlapped %p for handle %p (device %u.%u)",
+				overlapped, dev_handle, dev_handle->dev->bus_number, dev_handle->dev->device_address);
+			continue;
+		}
+
 		itransfer = (struct usbi_transfer *)((unsigned char *)transfer_priv + PTR_ALIGN(sizeof(*transfer_priv)));
-		usbi_dbg("transfer %p completed, length %lu",
+		usbi_dbg(ctx, "transfer %p completed, length %lu",
 			 USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer), ULONG_CAST(num_bytes));
 		usbi_signal_transfer_completion(itransfer);
 	}
 
-	usbi_dbg("I/O completion thread exiting");
+	usbi_dbg(ctx, "I/O completion thread exiting");
 
 	return 0;
 }
@@ -450,26 +501,9 @@
 static int windows_init(struct libusb_context *ctx)
 {
 	struct windows_context_priv *priv = usbi_get_context_priv(ctx);
-	char mutex_name[11 + 8 + 1]; // strlen("libusb_init") + (32-bit hex PID) + '\0'
-	HANDLE mutex;
 	bool winusb_backend_init = false;
 	int r;
 
-	sprintf(mutex_name, "libusb_init%08lX", ULONG_CAST(GetCurrentProcessId() & 0xFFFFFFFFU));
-	mutex = CreateMutexA(NULL, FALSE, mutex_name);
-	if (mutex == NULL) {
-		usbi_err(ctx, "could not create mutex: %s", windows_error_str(0));
-		return LIBUSB_ERROR_NO_MEM;
-	}
-
-	// A successful wait gives this thread ownership of the mutex
-	// => any concurrent wait stalls until the mutex is released
-	if (WaitForSingleObject(mutex, INFINITE) != WAIT_OBJECT_0) {
-		usbi_err(ctx, "failure to access mutex: %s", windows_error_str(0));
-		CloseHandle(mutex);
-		return LIBUSB_ERROR_NO_MEM;
-	}
-
 	// NB: concurrent usage supposes that init calls are equally balanced with
 	// exit calls. If init is called more than exit, we will not exit properly
 	if (++init_count == 1) { // First init?
@@ -496,7 +530,7 @@
 
 		r = usbdk_backend.init(ctx);
 		if (r == LIBUSB_SUCCESS) {
-			usbi_dbg("UsbDk backend is available");
+			usbi_dbg(ctx, "UsbDk backend is available");
 			usbdk_available = true;
 		} else {
 			usbi_info(ctx, "UsbDk backend is not available");
@@ -538,29 +572,12 @@
 		--init_count;
 	}
 
-	ReleaseMutex(mutex);
-	CloseHandle(mutex);
 	return r;
 }
 
 static void windows_exit(struct libusb_context *ctx)
 {
 	struct windows_context_priv *priv = usbi_get_context_priv(ctx);
-	char mutex_name[11 + 8 + 1]; // strlen("libusb_init") + (32-bit hex PID) + '\0'
-	HANDLE mutex;
-
-	sprintf(mutex_name, "libusb_init%08lX", ULONG_CAST(GetCurrentProcessId() & 0xFFFFFFFFU));
-	mutex = CreateMutexA(NULL, FALSE, mutex_name);
-	if (mutex == NULL)
-		return;
-
-	// A successful wait gives this thread ownership of the mutex
-	// => any concurrent wait stalls until the mutex is released
-	if (WaitForSingleObject(mutex, INFINITE) != WAIT_OBJECT_0) {
-		usbi_err(ctx, "failed to access mutex: %s", windows_error_str(0));
-		CloseHandle(mutex);
-		return;
-	}
 
 	// A NULL completion status will indicate to the thread that it is time to exit
 	if (!PostQueuedCompletionStatus(priv->completion_port, 0, (ULONG_PTR)ctx, NULL))
@@ -581,9 +598,6 @@
 		winusb_backend.exit(ctx);
 		htab_destroy();
 	}
-
-	ReleaseMutex(mutex);
-	CloseHandle(mutex);
 }
 
 static int windows_set_option(struct libusb_context *ctx, enum libusb_option option, va_list ap)
@@ -597,7 +611,7 @@
 			usbi_err(ctx, "UsbDk backend not available");
 			return LIBUSB_ERROR_NOT_FOUND;
 		}
-		usbi_dbg("switching context %p to use UsbDk backend", ctx);
+		usbi_dbg(ctx, "switching context %p to use UsbDk backend", ctx);
 		priv->backend = &usbdk_backend;
 		return LIBUSB_SUCCESS;
 	}
@@ -614,6 +628,9 @@
 static int windows_open(struct libusb_device_handle *dev_handle)
 {
 	struct windows_context_priv *priv = usbi_get_context_priv(HANDLE_CTX(dev_handle));
+	struct windows_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle);
+
+	list_init(&handle_priv->active_transfers);
 	return priv->backend->open(dev_handle);
 }
 
@@ -698,8 +715,10 @@
 static int windows_submit_transfer(struct usbi_transfer *itransfer)
 {
 	struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
-	struct libusb_context *ctx = TRANSFER_CTX(transfer);
+	struct libusb_device_handle *dev_handle = transfer->dev_handle;
+	struct libusb_context *ctx = HANDLE_CTX(dev_handle);
 	struct windows_context_priv *priv = usbi_get_context_priv(ctx);
+	struct windows_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle);
 	struct windows_transfer_priv *transfer_priv = usbi_get_transfer_priv(itransfer);
 	int r;
 
@@ -722,8 +741,18 @@
 		transfer_priv->handle = NULL;
 	}
 
+	// Add transfer to the device handle's list
+	usbi_mutex_lock(&dev_handle->lock);
+	list_add_tail(&transfer_priv->list, &handle_priv->active_transfers);
+	usbi_mutex_unlock(&dev_handle->lock);
+
 	r = priv->backend->submit_transfer(itransfer);
 	if (r != LIBUSB_SUCCESS) {
+		// Remove the unsuccessful transfer from the device handle's list
+		usbi_mutex_lock(&dev_handle->lock);
+		list_del(&transfer_priv->list);
+		usbi_mutex_unlock(&dev_handle->lock);
+
 		// Always call the backend's clear_transfer_priv() function on failure
 		priv->backend->clear_transfer_priv(itransfer);
 		transfer_priv->handle = NULL;
@@ -772,7 +801,7 @@
 	else
 		result = GetLastError();
 
-	usbi_dbg("handling transfer %p completion with errcode %lu, length %lu",
+	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));
 
 	switch (result) {
@@ -780,25 +809,25 @@
 		status = backend->copy_transfer_data(itransfer, bytes_transferred);
 		break;
 	case ERROR_GEN_FAILURE:
-		usbi_dbg("detected endpoint stall");
+		usbi_dbg(ctx, "detected endpoint stall");
 		status = LIBUSB_TRANSFER_STALL;
 		break;
 	case ERROR_SEM_TIMEOUT:
-		usbi_dbg("detected semaphore timeout");
+		usbi_dbg(ctx, "detected semaphore timeout");
 		status = LIBUSB_TRANSFER_TIMED_OUT;
 		break;
 	case ERROR_OPERATION_ABORTED:
 		istatus = backend->copy_transfer_data(itransfer, bytes_transferred);
 		if (istatus != LIBUSB_TRANSFER_COMPLETED)
-			usbi_dbg("failed to copy partial data in aborted operation: %d", (int)istatus);
+			usbi_dbg(ctx, "failed to copy partial data in aborted operation: %d", (int)istatus);
 
-		usbi_dbg("detected operation aborted");
+		usbi_dbg(ctx, "detected operation aborted");
 		status = LIBUSB_TRANSFER_CANCELLED;
 		break;
 	case ERROR_FILE_NOT_FOUND:
 	case ERROR_DEVICE_NOT_CONNECTED:
 	case ERROR_NO_SUCH_DEVICE:
-		usbi_dbg("detected device removed");
+		usbi_dbg(ctx, "detected device removed");
 		status = LIBUSB_TRANSFER_NO_DEVICE;
 		break;
 	default:
@@ -881,6 +910,6 @@
 	windows_handle_transfer_completion,
 	sizeof(struct windows_context_priv),
 	sizeof(union windows_device_priv),
-	sizeof(union windows_device_handle_priv),
+	sizeof(struct windows_device_handle_priv),
 	sizeof(struct windows_transfer_priv),
 };
diff --git a/libusb/os/windows_common.h b/libusb/os/windows_common.h
index 0c4b94c..4582ce4 100644
--- a/libusb/os/windows_common.h
+++ b/libusb/os/windows_common.h
@@ -52,6 +52,8 @@
 #define _strdup strdup
 // _beginthreadex is MSVCRT => unavailable for cygwin. Fallback to using CreateThread
 #define _beginthreadex(a, b, c, d, e, f) CreateThread(a, b, (LPTHREAD_START_ROUTINE)c, d, e, (LPDWORD)f)
+#else
+#include <process.h>
 #endif
 
 #define safe_free(p) do {if (p != NULL) {free((void *)p); p = NULL;}} while (0)
@@ -151,7 +153,8 @@
 	WINDOWS_8,
 	WINDOWS_8_1,
 	WINDOWS_10,
-	WINDOWS_11_OR_LATER
+	WINDOWS_11,
+	WINDOWS_12_OR_LATER
 };
 
 extern enum windows_version windows_version;
@@ -257,18 +260,26 @@
 	} usb_interface[USB_MAXINTERFACES];
 	struct hid_device_priv *hid;
 	PUSB_CONFIGURATION_DESCRIPTOR *config_descriptor; // list of pointers to the cached config descriptors
+	GUID class_guid; // checked for change during re-enumeration
 };
 
 struct usbdk_device_handle_priv {
 	// Not currently used
 	char dummy;
 };
+ 
+enum WINUSB_ZLP {
+	WINUSB_ZLP_UNSET = 0,
+	WINUSB_ZLP_OFF = 1,
+	WINUSB_ZLP_ON = 2
+};
 
 struct winusb_device_handle_priv {
 	int active_interface;
 	struct {
 		HANDLE dev_handle; // WinUSB needs an extra handle for the file
 		HANDLE api_handle; // used by the API to communicate with the device
+		uint8_t zlp[USB_MAXENDPOINTS]; // Current per-endpoint SHORT_PACKET_TERMINATE status (enum WINUSB_ZLP)
 	} interface_handle[USB_MAXINTERFACES];
 	int autoclaim_count[USB_MAXINTERFACES]; // For auto-release
 };
@@ -336,20 +347,36 @@
 	struct winusb_device_priv winusb_priv;
 };
 
-union windows_device_handle_priv {
-	struct usbdk_device_handle_priv usbdk_priv;
-	struct winusb_device_handle_priv winusb_priv;
+struct windows_device_handle_priv {
+	struct list_head active_transfers;
+	union {
+		struct usbdk_device_handle_priv usbdk_priv;
+		struct winusb_device_handle_priv winusb_priv;
+	};
 };
 
 struct windows_transfer_priv {
 	OVERLAPPED overlapped;
 	HANDLE handle;
+	struct list_head list;
 	union {
 		struct usbdk_transfer_priv usbdk_priv;
 		struct winusb_transfer_priv winusb_priv;
 	};
 };
 
+static inline struct usbdk_device_handle_priv *get_usbdk_device_handle_priv(struct libusb_device_handle *dev_handle)
+{
+	struct windows_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle);
+	return &handle_priv->usbdk_priv;
+}
+
+static inline struct winusb_device_handle_priv *get_winusb_device_handle_priv(struct libusb_device_handle *dev_handle)
+{
+	struct windows_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle);
+	return &handle_priv->winusb_priv;
+}
+
 static inline OVERLAPPED *get_transfer_priv_overlapped(struct usbi_transfer *itransfer)
 {
 	struct windows_transfer_priv *transfer_priv = usbi_get_transfer_priv(itransfer);
diff --git a/libusb/os/windows_usbdk.c b/libusb/os/windows_usbdk.c
index c9ebfcf..9f52b48 100644
--- a/libusb/os/windows_usbdk.c
+++ b/libusb/os/windows_usbdk.c
@@ -27,7 +27,6 @@
 #include <stdio.h>
 
 #include "libusbi.h"
-#include "windows_common.h"
 #include "windows_usbdk.h"
 
 #if !defined(STATUS_SUCCESS)
@@ -414,7 +413,7 @@
 
 	device_priv->system_handle = usbdk_helper.GetRedirectorSystemHandle(device_priv->redirector_handle);
 
-	if (CreateIoCompletionPort(device_priv->system_handle, priv->completion_port, 0, 0) == NULL) {
+	if (CreateIoCompletionPort(device_priv->system_handle, priv->completion_port, (ULONG_PTR)dev_handle, 0) == NULL) {
 		usbi_err(ctx, "failed to associate handle to I/O completion port: %s", windows_error_str(0));
 		usbdk_helper.StopRedirect(device_priv->redirector_handle);
 		device_priv->system_handle = NULL;
diff --git a/libusb/os/windows_winusb.c b/libusb/os/windows_winusb.c
index f291b8e..ffc1612 100644
--- a/libusb/os/windows_winusb.c
+++ b/libusb/os/windows_winusb.c
@@ -28,14 +28,9 @@
 #include <windows.h>
 #include <setupapi.h>
 #include <ctype.h>
-#include <fcntl.h>
-#include <process.h>
 #include <stdio.h>
-#include <objbase.h>
-#include <winioctl.h>
 
 #include "libusbi.h"
-#include "windows_common.h"
 #include "windows_winusb.h"
 
 #define HANDLE_VALID(h) (((h) != NULL) && ((h) != INVALID_HANDLE_VALUE))
@@ -109,12 +104,12 @@
 	} while (0)
 
 #if defined(ENABLE_LOGGING)
-static const char *guid_to_string(const GUID *guid)
+static const char *guid_to_string(const GUID *guid, char guid_string[MAX_GUID_STRING_LENGTH])
 {
-	static char guid_string[MAX_GUID_STRING_LENGTH];
-
-	if (guid == NULL)
-		return "";
+	if (guid == NULL) {
+		guid_string[0] = '\0';
+		return guid_string;
+	}
 
 	sprintf(guid_string, "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}",
 		(unsigned int)guid->Data1, guid->Data2, guid->Data3,
@@ -125,6 +120,37 @@
 }
 #endif
 
+static bool string_to_guid(const char guid_string[MAX_GUID_STRING_LENGTH], GUID *guid)
+{
+	unsigned short tmp[4];
+	int num_chars = -1;
+	char extra;
+	int r;
+
+	// Unfortunately MinGW complains that '%hhx' is not a valid format specifier,
+	// even though Visual Studio 2013 and later support it. Rather than complicating
+	// the logic in this function with '#ifdef's, use a temporary array on the stack
+	// to store the conversions.
+	r = sscanf(guid_string, "{%8x-%4hx-%4hx-%4hx-%4hx%4hx%4hx}%n%c",
+		(unsigned int *)&guid->Data1, &guid->Data2, &guid->Data3,
+		&tmp[0], &tmp[1], &tmp[2], &tmp[3], &num_chars, &extra);
+
+	if ((r != 7) || (num_chars != 38))
+		return false;
+
+	// Extract the bytes from the 2-byte shorts
+	guid->Data4[0] = (unsigned char)((tmp[0] >> 8) & 0xFF);
+	guid->Data4[1] = (unsigned char)(tmp[0] & 0xFF);
+	guid->Data4[2] = (unsigned char)((tmp[1] >> 8) & 0xFF);
+	guid->Data4[3] = (unsigned char)(tmp[1] & 0xFF);
+	guid->Data4[4] = (unsigned char)((tmp[2] >> 8) & 0xFF);
+	guid->Data4[5] = (unsigned char)(tmp[2] & 0xFF);
+	guid->Data4[6] = (unsigned char)((tmp[3] >> 8) & 0xFF);
+	guid->Data4[7] = (unsigned char)(tmp[3] & 0xFF);
+
+	return true;
+}
+
 /*
  * Normalize Microsoft's paths: return a duplicate of the given path
  * with all characters converted to uppercase
@@ -154,12 +180,9 @@
 
 	// Prefixed to avoid conflict with header files
 	DLL_GET_HANDLE(ctx, AdvAPI32);
-	DLL_LOAD_FUNC_PREFIXED(AdvAPI32, p, RegQueryValueExW, true);
+	DLL_LOAD_FUNC_PREFIXED(AdvAPI32, p, RegQueryValueExA, true);
 	DLL_LOAD_FUNC_PREFIXED(AdvAPI32, p, RegCloseKey, true);
 
-	DLL_GET_HANDLE(ctx, OLE32);
-	DLL_LOAD_FUNC_PREFIXED(OLE32, p, IIDFromString, true);
-
 	DLL_GET_HANDLE(ctx, SetupAPI);
 	DLL_LOAD_FUNC_PREFIXED(SetupAPI, p, SetupDiGetClassDevsA, true);
 	DLL_LOAD_FUNC_PREFIXED(SetupAPI, p, SetupDiEnumDeviceInfo, true);
@@ -177,7 +200,6 @@
 static void exit_dlls(void)
 {
 	DLL_FREE_HANDLE(SetupAPI);
-	DLL_FREE_HANDLE(OLE32);
 	DLL_FREE_HANDLE(AdvAPI32);
 	DLL_FREE_HANDLE(Cfgmgr32);
 }
@@ -238,6 +260,7 @@
 {
 	SP_DEVICE_INTERFACE_DATA dev_interface_data;
 	PSP_DEVICE_INTERFACE_DETAIL_DATA_A dev_interface_details;
+	char guid_string[MAX_GUID_STRING_LENGTH];
 	DWORD size;
 
 	dev_info_data->cbSize = sizeof(SP_DEVINFO_DATA);
@@ -246,7 +269,7 @@
 		if (!pSetupDiEnumDeviceInfo(dev_info, *_index, dev_info_data)) {
 			if (GetLastError() != ERROR_NO_MORE_ITEMS) {
 				usbi_err(ctx, "Could not obtain device info data for %s index %lu: %s",
-					guid_to_string(guid), ULONG_CAST(*_index), windows_error_str(0));
+					guid_to_string(guid, guid_string), ULONG_CAST(*_index), windows_error_str(0));
 				return LIBUSB_ERROR_OTHER;
 			}
 
@@ -262,7 +285,7 @@
 
 		if (GetLastError() != ERROR_NO_MORE_ITEMS) {
 			usbi_err(ctx, "Could not obtain interface data for %s devInst %lX: %s",
-				guid_to_string(guid), ULONG_CAST(dev_info_data->DevInst), windows_error_str(0));
+				guid_to_string(guid, guid_string), ULONG_CAST(dev_info_data->DevInst), windows_error_str(0));
 			return LIBUSB_ERROR_OTHER;
 		}
 
@@ -274,7 +297,7 @@
 		// The dummy call should fail with ERROR_INSUFFICIENT_BUFFER
 		if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
 			usbi_err(ctx, "could not access interface data (dummy) for %s devInst %lX: %s",
-				guid_to_string(guid), ULONG_CAST(dev_info_data->DevInst), windows_error_str(0));
+				guid_to_string(guid, guid_string), ULONG_CAST(dev_info_data->DevInst), windows_error_str(0));
 			return LIBUSB_ERROR_OTHER;
 		}
 	} else {
@@ -285,7 +308,7 @@
 	dev_interface_details = malloc(size);
 	if (dev_interface_details == NULL) {
 		usbi_err(ctx, "could not allocate interface data for %s devInst %lX",
-			guid_to_string(guid), ULONG_CAST(dev_info_data->DevInst));
+			guid_to_string(guid, guid_string), ULONG_CAST(dev_info_data->DevInst));
 		return LIBUSB_ERROR_NO_MEM;
 	}
 
@@ -293,7 +316,7 @@
 	if (!pSetupDiGetDeviceInterfaceDetailA(dev_info, &dev_interface_data,
 		dev_interface_details, size, NULL, NULL)) {
 		usbi_err(ctx, "could not access interface data (actual) for %s devInst %lX: %s",
-			guid_to_string(guid), ULONG_CAST(dev_info_data->DevInst), windows_error_str(0));
+			guid_to_string(guid, guid_string), ULONG_CAST(dev_info_data->DevInst), windows_error_str(0));
 		free(dev_interface_details);
 		return LIBUSB_ERROR_OTHER;
 	}
@@ -303,7 +326,7 @@
 
 	if (*dev_interface_path == NULL) {
 		usbi_err(ctx, "could not allocate interface path for %s devInst %lX",
-			guid_to_string(guid), ULONG_CAST(dev_info_data->DevInst));
+			guid_to_string(guid, guid_string), ULONG_CAST(dev_info_data->DevInst));
 		return LIBUSB_ERROR_NO_MEM;
 	}
 
@@ -386,14 +409,14 @@
 		DWORD value_length = sizeof(DWORD);
 		LONG status;
 
-		status = pRegQueryValueExW(hkey_dev_interface, L"LUsb0", NULL, NULL,
+		status = pRegQueryValueExA(hkey_dev_interface, "LUsb0", NULL, NULL,
 			(LPBYTE)&libusb0_symboliclink_index, &value_length);
 		if (status == ERROR_SUCCESS) {
 			if (libusb0_symboliclink_index < 256) {
 				// libusb0.sys is connected to this device instance.
 				// If the the device interface guid is {F9F3FF14-AE21-48A0-8A25-8011A7A931D9} then it's a filter.
 				sprintf(filter_path, "\\\\.\\libusb0-%04u", (unsigned int)libusb0_symboliclink_index);
-				usbi_dbg("assigned libusb0 symbolic link %s", filter_path);
+				usbi_dbg(ctx, "assigned libusb0 symbolic link %s", filter_path);
 			} else {
 				// libusb0.sys was connected to this device instance at one time; but not anymore.
 			}
@@ -451,23 +474,23 @@
 			intf_desc = &intf->altsetting[j];
 			for (k = 0; k < intf_desc->bNumEndpoints; k++) {
 				if (intf_desc->endpoint[k].bEndpointAddress == ep) {
-					usbi_dbg("found endpoint %02X on interface %d", intf_desc->bInterfaceNumber, i);
+					usbi_dbg(NULL, "found endpoint %02X on interface %d", intf_desc->bInterfaceNumber, i);
 					return intf_desc->bInterfaceNumber;
 				}
 			}
 		}
 	}
 
-	usbi_dbg("endpoint %02X not found on any interface", ep);
+	usbi_dbg(NULL, "endpoint %02X not found on any interface", ep);
 	return LIBUSB_ERROR_NOT_FOUND;
 }
 
 /*
  * Open a device and associate the HANDLE with the context's I/O completion port
  */
-static HANDLE windows_open(struct libusb_device *dev, const char *path, DWORD access)
+static HANDLE windows_open(struct libusb_device_handle *dev_handle, const char *path, DWORD access)
 {
-	struct libusb_context *ctx = DEVICE_CTX(dev);
+	struct libusb_context *ctx = HANDLE_CTX(dev_handle);
 	struct windows_context_priv *priv = usbi_get_context_priv(ctx);
 	HANDLE handle;
 
@@ -475,7 +498,7 @@
 	if (handle == INVALID_HANDLE_VALUE)
 		return handle;
 
-	if (CreateIoCompletionPort(handle, priv->completion_port, 0, 0) == NULL) {
+	if (CreateIoCompletionPort(handle, priv->completion_port, (ULONG_PTR)dev_handle, 0) == NULL) {
 		usbi_err(ctx, "failed to associate handle to I/O completion port: %s", windows_error_str(0));
 		CloseHandle(handle);
 		return INVALID_HANDLE_VALUE;
@@ -500,26 +523,26 @@
 		return r;
 	}
 
+	if (iface >= conf_desc->bNumInterfaces) {
+		usbi_err(HANDLE_CTX(dev_handle), "interface %d out of range for device", iface);
+		return LIBUSB_ERROR_NOT_FOUND;
+	}
 	if_desc = &conf_desc->interface[iface].altsetting[altsetting];
 	safe_free(priv->usb_interface[iface].endpoint);
 
 	if (if_desc->bNumEndpoints == 0) {
-		usbi_dbg("no endpoints found for interface %u", iface);
-		libusb_free_config_descriptor(conf_desc);
-		priv->usb_interface[iface].current_altsetting = altsetting;
-		return LIBUSB_SUCCESS;
-	}
-
-	priv->usb_interface[iface].endpoint = malloc(if_desc->bNumEndpoints);
-	if (priv->usb_interface[iface].endpoint == NULL) {
-		libusb_free_config_descriptor(conf_desc);
-		return LIBUSB_ERROR_NO_MEM;
-	}
-
-	priv->usb_interface[iface].nb_endpoints = if_desc->bNumEndpoints;
-	for (i = 0; i < if_desc->bNumEndpoints; i++) {
-		priv->usb_interface[iface].endpoint[i] = if_desc->endpoint[i].bEndpointAddress;
-		usbi_dbg("(re)assigned endpoint %02X to interface %u", priv->usb_interface[iface].endpoint[i], iface);
+		usbi_dbg(HANDLE_CTX(dev_handle), "no endpoints found for interface %u", iface);
+	} else {
+		priv->usb_interface[iface].endpoint = malloc(if_desc->bNumEndpoints);
+		if (priv->usb_interface[iface].endpoint == NULL) {
+			libusb_free_config_descriptor(conf_desc);
+			return LIBUSB_ERROR_NO_MEM;
+		}
+		priv->usb_interface[iface].nb_endpoints = if_desc->bNumEndpoints;
+		for (i = 0; i < if_desc->bNumEndpoints; i++) {
+			priv->usb_interface[iface].endpoint[i] = if_desc->endpoint[i].bEndpointAddress;
+			usbi_dbg(HANDLE_CTX(dev_handle), "(re)assigned endpoint %02X to interface %u", priv->usb_interface[iface].endpoint[i], iface);
+		}
 	}
 	libusb_free_config_descriptor(conf_desc);
 
@@ -570,7 +593,7 @@
 static int auto_claim(struct libusb_transfer *transfer, int *interface_number, int api_type)
 {
 	struct winusb_device_handle_priv *handle_priv =
-		usbi_get_device_handle_priv(transfer->dev_handle);
+		get_winusb_device_handle_priv(transfer->dev_handle);
 	struct winusb_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev);
 	int current_interface = *interface_number;
 	int r = LIBUSB_SUCCESS;
@@ -589,7 +612,7 @@
 			// Must claim an interface of the same API type
 			if ((priv->usb_interface[current_interface].apib->id == api_type)
 					&& (libusb_claim_interface(transfer->dev_handle, current_interface) == LIBUSB_SUCCESS)) {
-				usbi_dbg("auto-claimed interface %d for control request", current_interface);
+				usbi_dbg(TRANSFER_CTX(transfer), "auto-claimed interface %d for control request", current_interface);
 				if (handle_priv->autoclaim_count[current_interface] != 0)
 					usbi_err(TRANSFER_CTX(transfer), "program assertion failed - autoclaim_count was nonzero");
 				handle_priv->autoclaim_count[current_interface]++;
@@ -617,7 +640,7 @@
 	struct winusb_transfer_priv *transfer_priv = get_winusb_transfer_priv(itransfer);
 	struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
 	libusb_device_handle *dev_handle = transfer->dev_handle;
-	struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle);
+	struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle);
 	int r;
 
 	usbi_mutex_lock(&autoclaim_lock);
@@ -626,9 +649,9 @@
 		if (handle_priv->autoclaim_count[transfer_priv->interface_number] == 0) {
 			r = libusb_release_interface(dev_handle, transfer_priv->interface_number);
 			if (r == LIBUSB_SUCCESS)
-				usbi_dbg("auto-released interface %d", transfer_priv->interface_number);
+				usbi_dbg(ITRANSFER_CTX(itransfer), "auto-released interface %d", transfer_priv->interface_number);
 			else
-				usbi_dbg("failed to auto-release interface %d (%s)",
+				usbi_dbg(ITRANSFER_CTX(itransfer), "failed to auto-release interface %d (%s)",
 					transfer_priv->interface_number, libusb_error_name((enum libusb_error)r));
 		}
 	}
@@ -769,7 +792,7 @@
 			continue;
 		}
 
-		usbi_dbg("cached config descriptor %u (bConfigurationValue=%u, %u bytes)",
+		usbi_dbg(ctx, "cached config descriptor %u (bConfigurationValue=%u, %u bytes)",
 			i, cd_data->bConfigurationValue, cd_data->wTotalLength);
 
 		// Cache the descriptor
@@ -886,7 +909,7 @@
 	}
 
 	num_ports = hub_info.u.HubInformation.HubDescriptor.bNumberOfPorts;
-	usbi_dbg("root hub '%s' reports %lu ports", priv->dev_id, ULONG_CAST(num_ports));
+	usbi_dbg(ctx, "root hub '%s' reports %lu ports", priv->dev_id, ULONG_CAST(num_ports));
 
 	if (windows_version >= WINDOWS_8) {
 		// Windows 8 and later is better at reporting the speed capabilities of the root hub,
@@ -1021,7 +1044,7 @@
 static int init_device(struct libusb_device *dev, struct libusb_device *parent_dev,
 	uint8_t port_number, DEVINST devinst)
 {
-	struct libusb_context *ctx;
+	struct libusb_context *ctx = NULL;
 	struct libusb_device *tmp_dev;
 	struct winusb_device_priv *priv, *parent_priv, *tmp_priv;
 	USB_NODE_CONNECTION_INFORMATION_EX conn_info;
@@ -1120,7 +1143,7 @@
 
 			priv->active_config = conn_info.CurrentConfigurationValue;
 			if (priv->active_config == 0) {
-				usbi_dbg("0x%x:0x%x found %u configurations (not configured)",
+				usbi_dbg(ctx, "0x%x:0x%x found %u configurations (not configured)",
 					dev->device_descriptor.idVendor,
 					dev->device_descriptor.idProduct,
 					dev->device_descriptor.bNumConfigurations);
@@ -1143,7 +1166,7 @@
 				dev->device_descriptor.bNumConfigurations);
 			priv->active_config = 1;
 		} else {
-			usbi_dbg("found %u configurations (current config: %u)", dev->device_descriptor.bNumConfigurations, priv->active_config);
+			usbi_dbg(ctx, "found %u configurations (current config: %u)", dev->device_descriptor.bNumConfigurations, priv->active_config);
 		}
 
 		// Cache as many config descriptors as we can
@@ -1194,12 +1217,53 @@
 
 	priv->initialized = true;
 
-	usbi_dbg("(bus: %u, addr: %u, depth: %u, port: %u): '%s'",
+	usbi_dbg(ctx, "(bus: %u, addr: %u, depth: %u, port: %u): '%s'",
 		dev->bus_number, dev->device_address, priv->depth, dev->port_number, priv->dev_id);
 
 	return LIBUSB_SUCCESS;
 }
 
+static bool get_dev_port_number(HDEVINFO dev_info, SP_DEVINFO_DATA *dev_info_data, DWORD *port_nr)
+{
+	char buffer[MAX_KEY_LENGTH];
+	DWORD size;
+
+	// First try SPDRP_LOCATION_INFORMATION, which returns a REG_SZ. The string *may* have a format
+	// similar to "Port_#0002.Hub_#000D", in which case we can extract the port number. However, we
+	// cannot extract the port if the returned string does not follow this format.
+	if (pSetupDiGetDeviceRegistryPropertyA(dev_info, dev_info_data, SPDRP_LOCATION_INFORMATION,
+			NULL, (PBYTE)buffer, sizeof(buffer), NULL)) {
+		// Check for the required format.
+		if (strncmp(buffer, "Port_#", 6) == 0) {
+			*port_nr = atoi(buffer + 6);
+			return true;
+		}
+	}
+
+	// Next try SPDRP_LOCATION_PATHS, which returns a REG_MULTI_SZ (but we only examine the first
+	// string in it). Each path has a format similar to,
+	// "PCIROOT(B2)#PCI(0300)#PCI(0000)#USBROOT(0)#USB(1)#USB(2)#USBMI(3)", and the port number is
+	// the number within the last "USB(x)" token.
+	if (pSetupDiGetDeviceRegistryPropertyA(dev_info, dev_info_data, SPDRP_LOCATION_PATHS,
+			NULL, (PBYTE)buffer, sizeof(buffer), NULL)) {
+		// Find the last "#USB(x)" substring
+		for (char *token = strrchr(buffer, '#'); token != NULL; token = strrchr(buffer, '#')) {
+			if (strncmp(token, "#USB(", 5) == 0) {
+				*port_nr = atoi(token + 5);
+				return true;
+			}
+			// Shorten the string and try again.
+			*token = '\0';
+		}
+	}
+
+	// Lastly, try SPDRP_ADDRESS, which returns a REG_DWORD. The address *may* be the port number,
+	// which is true for the Microsoft driver but may not be true for other drivers. However, we
+	// have no other options here but to accept what it returns.
+	return pSetupDiGetDeviceRegistryPropertyA(dev_info, dev_info_data, SPDRP_ADDRESS,
+			NULL, (PBYTE)port_nr, sizeof(*port_nr), &size) && (size == sizeof(*port_nr));
+}
+
 static int enumerate_hcd_root_hub(struct libusb_context *ctx, const char *dev_id,
 	uint8_t bus_number, DEVINST devinst)
 {
@@ -1222,7 +1286,7 @@
 
 	if (dev->bus_number == 0) {
 		// Only do this once
-		usbi_dbg("assigning HCD '%s' bus number %u", dev_id, bus_number);
+		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)
@@ -1266,10 +1330,10 @@
 				if (lookup[k].list[l] == 0)
 					lookup[k].list[l] = LIST_SEPARATOR;
 			}
-			usbi_dbg("%s(s): %s", lookup[k].designation, lookup[k].list);
+			usbi_dbg(NULL, "%s(s): %s", lookup[k].designation, lookup[k].list);
 		} else {
 			if (GetLastError() != ERROR_INVALID_DATA)
-				usbi_dbg("could not access %s: %s", lookup[k].designation, windows_error_str(0));
+				usbi_dbg(NULL, "could not access %s: %s", lookup[k].designation, windows_error_str(0));
 			lookup[k].list[0] = 0;
 		}
 	}
@@ -1278,7 +1342,7 @@
 		for (k = 0; k < 3; k++) {
 			j = get_sub_api(lookup[k].list, i);
 			if (j >= 0) {
-				usbi_dbg("matched %s name against %s", lookup[k].designation,
+				usbi_dbg(NULL, "matched %s name against %s", lookup[k].designation,
 					(i != USB_API_WINUSBX) ? usb_api_backend[i].designation : usb_api_backend[i].driver_name_list[j]);
 				*api = i;
 				*sub_api = j;
@@ -1314,7 +1378,7 @@
 	if (priv->usb_interface[interface_number].path != NULL) {
 		if (api == USB_API_HID) {
 			// HID devices can have multiple collections (COL##) for each MI_## interface
-			usbi_dbg("interface[%d] already set - ignoring HID collection: %s",
+			usbi_dbg(ctx, "interface[%d] already set - ignoring HID collection: %s",
 				interface_number, device_id);
 			return LIBUSB_ERROR_ACCESS;
 		}
@@ -1322,7 +1386,7 @@
 		safe_free(priv->usb_interface[interface_number].path);
 	}
 
-	usbi_dbg("interface[%d] = %s", interface_number, dev_interface_path);
+	usbi_dbg(ctx, "interface[%d] = %s", interface_number, dev_interface_path);
 	priv->usb_interface[interface_number].path = dev_interface_path;
 	priv->usb_interface[interface_number].apib = &usb_api_backend[api];
 	priv->usb_interface[interface_number].sub_api = sub_api;
@@ -1351,14 +1415,14 @@
 
 	for (i = 0; i < priv->hid->nb_interfaces; i++) {
 		if ((priv->usb_interface[i].path != NULL) && strcmp(priv->usb_interface[i].path, dev_interface_path) == 0) {
-			usbi_dbg("interface[%u] already set to %s", i, dev_interface_path);
+			usbi_dbg(ctx, "interface[%u] already set to %s", i, dev_interface_path);
 			return LIBUSB_ERROR_ACCESS;
 		}
 	}
 
 	priv->usb_interface[priv->hid->nb_interfaces].path = dev_interface_path;
 	priv->usb_interface[priv->hid->nb_interfaces].apib = &usb_api_backend[USB_API_HID];
-	usbi_dbg("interface[%u] = %s", priv->hid->nb_interfaces, dev_interface_path);
+	usbi_dbg(ctx, "interface[%u] = %s", priv->hid->nb_interfaces, dev_interface_path);
 	priv->hid->nb_interfaces++;
 	return LIBUSB_SUCCESS;
 }
@@ -1384,7 +1448,7 @@
 	unsigned long session_id;
 	DWORD size, port_nr, reg_type, install_state;
 	HKEY key;
-	WCHAR guid_string_w[MAX_GUID_STRING_LENGTH];
+	char guid_string[MAX_GUID_STRING_LENGTH];
 	GUID *if_guid;
 	LONG s;
 #define HUB_PASS 0
@@ -1454,7 +1518,7 @@
 //#define ENUM_DEBUG
 #if defined(ENABLE_LOGGING) && defined(ENUM_DEBUG)
 		const char * const passname[] = {"HUB", "DEV", "HCD", "GEN", "HID", "EXT"};
-		usbi_dbg("#### PROCESSING %ss %s", passname[MIN(pass, EXT_PASS)], guid_to_string(guid_list[pass]));
+		usbi_dbg(ctx, "#### PROCESSING %ss %s", passname[MIN(pass, EXT_PASS)], guid_to_string(guid_list[pass], guid_string));
 #endif
 		if ((pass == HID_PASS) && (guid_list[HID_PASS] == NULL))
 			continue;
@@ -1506,7 +1570,7 @@
 			}
 
 #ifdef ENUM_DEBUG
-			usbi_dbg("PRO: %s", dev_id);
+			usbi_dbg(ctx, "PRO: %s", dev_id);
 #endif
 
 			// Set API to use or get additional data from generic pass
@@ -1529,7 +1593,7 @@
 						break;
 				}
 				if (j == nb_usb_enumerators) {
-					usbi_dbg("found new PnP enumerator string '%s'", enumerator);
+					usbi_dbg(ctx, "found new PnP enumerator string '%s'", enumerator);
 					if (nb_usb_enumerators < ARRAYSIZE(usb_enumerator)) {
 						usb_enumerator[nb_usb_enumerators] = _strdup(enumerator);
 						if (usb_enumerator[nb_usb_enumerators] != NULL) {
@@ -1555,16 +1619,25 @@
 				if (key == INVALID_HANDLE_VALUE)
 					break;
 				// Look for both DeviceInterfaceGUIDs *and* DeviceInterfaceGUID, in that order
-				size = sizeof(guid_string_w);
-				s = pRegQueryValueExW(key, L"DeviceInterfaceGUIDs", NULL, &reg_type,
-					(LPBYTE)guid_string_w, &size);
+				// If multiple GUIDs just process the first and ignore the others
+				size = sizeof(guid_string);
+				s = pRegQueryValueExA(key, "DeviceInterfaceGUIDs", NULL, &reg_type,
+					(LPBYTE)guid_string, &size);
 				if (s == ERROR_FILE_NOT_FOUND)
-					s = pRegQueryValueExW(key, L"DeviceInterfaceGUID", NULL, &reg_type,
-						(LPBYTE)guid_string_w, &size);
+					s = pRegQueryValueExA(key, "DeviceInterfaceGUID", NULL, &reg_type,
+						(LPBYTE)guid_string, &size);
 				pRegCloseKey(key);
-				if ((s == ERROR_SUCCESS) &&
-				    (((reg_type == REG_SZ) && (size == (sizeof(guid_string_w) - sizeof(WCHAR)))) ||
-				     ((reg_type == REG_MULTI_SZ) && (size == sizeof(guid_string_w))))) {
+				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) {
@@ -1579,8 +1652,8 @@
 						usbi_err(ctx, "failed to alloc if_guid");
 						LOOP_BREAK(LIBUSB_ERROR_NO_MEM);
 					}
-					if (pIIDFromString(guid_string_w, if_guid) != 0) {
-						usbi_warn(ctx, "device '%s' has malformed DeviceInterfaceGUID string, skipping", dev_id);
+					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
@@ -1589,14 +1662,14 @@
 								break;
 						}
 						if (j == nb_guids) {
-							usbi_dbg("extra GUID: %s", guid_to_string(if_guid));
+							usbi_dbg(ctx, "extra GUID: %s", guid_string);
 							guid_list[nb_guids++] = if_guid;
 						} else {
 							// Duplicate, ignore
 							free(if_guid);
 						}
 					}
-				} else if (s == ERROR_SUCCESS) {
+				} else {
 					usbi_warn(ctx, "unexpected type/size of DeviceInterfaceGUID for '%s'", dev_id);
 				}
 				break;
@@ -1631,7 +1704,7 @@
 						libusb_unref_device(dev);
 					}
 
-					usbi_dbg("unlisted ancestor for '%s' (non USB HID, newly connected, etc.) - ignoring", dev_id);
+					usbi_dbg(ctx, "unlisted ancestor for '%s' (non USB HID, newly connected, etc.) - ignoring", dev_id);
 					continue;
 				}
 
@@ -1650,23 +1723,30 @@
 				dev = usbi_get_device_by_session_id(ctx, session_id);
 				if (dev == NULL) {
 				alloc_device:
-					usbi_dbg("allocating new device for session [%lX]", session_id);
+					usbi_dbg(ctx, "allocating new device for session [%lX]", session_id);
 					dev = usbi_alloc_device(ctx, session_id);
 					if (dev == NULL)
 						LOOP_BREAK(LIBUSB_ERROR_NO_MEM);
 
 					priv = winusb_device_priv_init(dev);
 					priv->dev_id = _strdup(dev_id);
+					priv->class_guid = dev_info_data.ClassGuid;
 					if (priv->dev_id == NULL) {
 						libusb_unref_device(dev);
 						LOOP_BREAK(LIBUSB_ERROR_NO_MEM);
 					}
 				} else {
-					usbi_dbg("found existing device for session [%lX]", session_id);
+					usbi_dbg(ctx, "found existing device for session [%lX]", session_id);
 
 					priv = usbi_get_device_priv(dev);
 					if (strcmp(priv->dev_id, dev_id) != 0) {
-						usbi_dbg("device instance ID for session [%lX] changed", session_id);
+						usbi_dbg(ctx, "device instance ID for session [%lX] changed", session_id);
+						usbi_disconnect_device(dev);
+						libusb_unref_device(dev);
+						goto alloc_device;
+					}
+					if (!IsEqualGUID(&priv->class_guid, &dev_info_data.ClassGuid)) {
+						usbi_dbg(ctx, "device class GUID for session [%lX] changed", session_id);
 						usbi_disconnect_device(dev);
 						libusb_unref_device(dev);
 						goto alloc_device;
@@ -1724,10 +1804,8 @@
 				r = enumerate_hcd_root_hub(ctx, dev_id, (uint8_t)(i + 1), dev_info_data.DevInst);
 				break;
 			case GEN_PASS:
-				// The SPDRP_ADDRESS for USB devices is the device port number on the hub
 				port_nr = 0;
-				if (!pSetupDiGetDeviceRegistryPropertyA(*dev_info, &dev_info_data, SPDRP_ADDRESS,
-						NULL, (PBYTE)&port_nr, sizeof(port_nr), &size) || (size != sizeof(port_nr)))
+				if (!get_dev_port_number(*dev_info, &dev_info_data, &port_nr))
 					usbi_warn(ctx, "could not retrieve port number for device '%s': %s", dev_id, windows_error_str(0));
 				r = init_device(dev, parent_dev, (uint8_t)port_nr, dev_info_data.DevInst);
 				if (r == LIBUSB_SUCCESS) {
@@ -1747,10 +1825,10 @@
 			default: // HID_PASS and later
 				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("setting HID interface for [%lX]:", parent_dev->session_data);
+						usbi_dbg(ctx, "setting HID interface for [%lX]:", parent_dev->session_data);
 						r = set_hid_interface(ctx, parent_dev, dev_interface_path);
 					} else {
-						usbi_dbg("setting composite interface for [%lX]:", parent_dev->session_data);
+						usbi_dbg(ctx, "setting composite interface for [%lX]:", parent_dev->session_data);
 						r = set_composite_interface(ctx, parent_dev, dev_interface_path, dev_id, api, sub_api);
 					}
 					switch (r) {
@@ -2001,8 +2079,6 @@
 		break;
 	case LIBUSB_TRANSFER_TYPE_BULK:
 	case LIBUSB_TRANSFER_TYPE_INTERRUPT:
-		if (IS_XFEROUT(transfer) && (transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET))
-			return LIBUSB_ERROR_NOT_SUPPORTED;
 		transfer_fn = priv->apib->submit_bulk_transfer;
 		break;
 	case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
@@ -2263,10 +2339,10 @@
 			KLIB_VERSION LibK_Version;
 
 			pLibK_GetVersion(&LibK_Version);
-			usbi_dbg("libusbK DLL found, version: %d.%d.%d.%d", LibK_Version.Major, LibK_Version.Minor,
+			usbi_dbg(ctx, "libusbK DLL found, version: %d.%d.%d.%d", LibK_Version.Major, LibK_Version.Minor,
 				LibK_Version.Micro, LibK_Version.Nano);
 		} else {
-			usbi_dbg("libusbK DLL found, version unknown");
+			usbi_dbg(ctx, "libusbK DLL found, version unknown");
 		}
 
 		pLibK_GetProcAddress = (LibK_GetProcAddress_t)GetProcAddress(hlibusbK, "LibK_GetProcAddress");
@@ -2352,7 +2428,7 @@
 static int winusbx_open(int sub_api, struct libusb_device_handle *dev_handle)
 {
 	struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev);
-	struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle);
+	struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle);
 	HANDLE file_handle;
 	int i;
 
@@ -2362,7 +2438,7 @@
 	for (i = 0; i < USB_MAXINTERFACES; i++) {
 		if ((priv->usb_interface[i].path != NULL)
 				&& (priv->usb_interface[i].apib->id == USB_API_WINUSBX)) {
-			file_handle = windows_open(dev_handle->dev, priv->usb_interface[i].path, GENERIC_READ | GENERIC_WRITE);
+			file_handle = windows_open(dev_handle, priv->usb_interface[i].path, GENERIC_READ | GENERIC_WRITE);
 			if (file_handle == INVALID_HANDLE_VALUE) {
 				usbi_err(HANDLE_CTX(dev_handle), "could not open device %s (interface %d): %s", priv->usb_interface[i].path, i, windows_error_str(0));
 				switch (GetLastError()) {
@@ -2384,7 +2460,7 @@
 
 static void winusbx_close(int sub_api, struct libusb_device_handle *dev_handle)
 {
-	struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle);
+	struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle);
 	struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev);
 	HANDLE handle;
 	int i;
@@ -2429,7 +2505,7 @@
 
 static int winusbx_configure_endpoints(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface)
 {
-	struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle);
+	struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle);
 	struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev);
 	HANDLE winusb_handle = handle_priv->interface_handle[iface].api_handle;
 	UCHAR policy;
@@ -2445,35 +2521,36 @@
 		endpoint_address = (i == -1) ? 0 : priv->usb_interface[iface].endpoint[i];
 		if (!WinUSBX[sub_api].SetPipePolicy(winusb_handle, endpoint_address,
 			PIPE_TRANSFER_TIMEOUT, sizeof(ULONG), &timeout))
-			usbi_dbg("failed to set PIPE_TRANSFER_TIMEOUT for control endpoint %02X", endpoint_address);
+			usbi_dbg(HANDLE_CTX(dev_handle), "failed to set PIPE_TRANSFER_TIMEOUT for control endpoint %02X", endpoint_address);
 
 		if ((i == -1) || (sub_api == SUB_API_LIBUSB0))
 			continue; // Other policies don't apply to control endpoint or libusb0
 
 		policy = false;
+		handle_priv->interface_handle[iface].zlp[endpoint_address] = WINUSB_ZLP_UNSET;
 		if (!WinUSBX[sub_api].SetPipePolicy(winusb_handle, endpoint_address,
 			SHORT_PACKET_TERMINATE, sizeof(UCHAR), &policy))
-			usbi_dbg("failed to disable SHORT_PACKET_TERMINATE for endpoint %02X", endpoint_address);
+			usbi_dbg(HANDLE_CTX(dev_handle), "failed to disable SHORT_PACKET_TERMINATE for endpoint %02X", endpoint_address);
 
 		if (!WinUSBX[sub_api].SetPipePolicy(winusb_handle, endpoint_address,
 			IGNORE_SHORT_PACKETS, sizeof(UCHAR), &policy))
-			usbi_dbg("failed to disable IGNORE_SHORT_PACKETS for endpoint %02X", endpoint_address);
+			usbi_dbg(HANDLE_CTX(dev_handle), "failed to disable IGNORE_SHORT_PACKETS for endpoint %02X", endpoint_address);
 
 		policy = true;
 		/* ALLOW_PARTIAL_READS must be enabled due to likely libusbK bug. See:
 		   https://sourceforge.net/mailarchive/message.php?msg_id=29736015 */
 		if (!WinUSBX[sub_api].SetPipePolicy(winusb_handle, endpoint_address,
 			ALLOW_PARTIAL_READS, sizeof(UCHAR), &policy))
-			usbi_dbg("failed to enable ALLOW_PARTIAL_READS for endpoint %02X", endpoint_address);
+			usbi_dbg(HANDLE_CTX(dev_handle), "failed to enable ALLOW_PARTIAL_READS for endpoint %02X", endpoint_address);
 
 		if (!WinUSBX[sub_api].SetPipePolicy(winusb_handle, endpoint_address,
 			AUTO_CLEAR_STALL, sizeof(UCHAR), &policy))
-			usbi_dbg("failed to enable AUTO_CLEAR_STALL for endpoint %02X", endpoint_address);
+			usbi_dbg(HANDLE_CTX(dev_handle), "failed to enable AUTO_CLEAR_STALL for endpoint %02X", endpoint_address);
 
 		if (sub_api == SUB_API_LIBUSBK) {
 			if (!WinUSBX[sub_api].SetPipePolicy(winusb_handle, endpoint_address,
 				ISO_ALWAYS_START_ASAP, sizeof(UCHAR), &policy))
-				usbi_dbg("failed to enable ISO_ALWAYS_START_ASAP for endpoint %02X", endpoint_address);
+				usbi_dbg(HANDLE_CTX(dev_handle), "failed to enable ISO_ALWAYS_START_ASAP for endpoint %02X", endpoint_address);
 		}
 	}
 
@@ -2483,7 +2560,7 @@
 static int winusbx_claim_interface(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface)
 {
 	struct libusb_context *ctx = HANDLE_CTX(dev_handle);
-	struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle);
+	struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle);
 	struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev);
 	bool is_using_usbccgp = (priv->apib->id == USB_API_COMPOSITE);
 	HDEVINFO dev_info;
@@ -2534,7 +2611,7 @@
 					*dev_interface_path_guid_start = '\0';
 
 					if (strncmp(dev_interface_path, priv->usb_interface[iface].path, strlen(dev_interface_path)) == 0) {
-						file_handle = windows_open(dev_handle->dev, filter_path, GENERIC_READ | GENERIC_WRITE);
+						file_handle = windows_open(dev_handle, filter_path, GENERIC_READ | GENERIC_WRITE);
 						if (file_handle != INVALID_HANDLE_VALUE) {
 							if (WinUSBX[sub_api].Initialize(file_handle, &winusb_handle)) {
 								// Replace the existing file handle with the working one
@@ -2591,7 +2668,7 @@
 		}
 		handle_priv->interface_handle[iface].dev_handle = handle_priv->interface_handle[0].dev_handle;
 	}
-	usbi_dbg("claimed interface %u", iface);
+	usbi_dbg(ctx, "claimed interface %u", iface);
 	handle_priv->active_interface = iface;
 
 	return LIBUSB_SUCCESS;
@@ -2599,7 +2676,7 @@
 
 static int winusbx_release_interface(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface)
 {
-	struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle);
+	struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle);
 	struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev);
 	HANDLE winusb_handle;
 
@@ -2620,12 +2697,12 @@
  */
 static int get_valid_interface(struct libusb_device_handle *dev_handle, int api_id)
 {
-	struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle);
+	struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle);
 	struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev);
 	int i;
 
 	if ((api_id < USB_API_WINUSBX) || (api_id > USB_API_HID)) {
-		usbi_dbg("unsupported API ID");
+		usbi_dbg(HANDLE_CTX(dev_handle), "unsupported API ID");
 		return -1;
 	}
 
@@ -2644,14 +2721,14 @@
 */
 static int check_valid_interface(struct libusb_device_handle *dev_handle, unsigned short interface, int api_id)
 {
-	struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle);
+	struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle);
 	struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev);
 
 	if (interface >= USB_MAXINTERFACES)
 		return -1;
 
 	if ((api_id < USB_API_WINUSBX) || (api_id > USB_API_HID)) {
-		usbi_dbg("unsupported API ID");
+		usbi_dbg(HANDLE_CTX(dev_handle), "unsupported API ID");
 		return -1;
 	}
 
@@ -2691,9 +2768,9 @@
 	struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
 	struct winusb_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev);
 	struct winusb_transfer_priv *transfer_priv = get_winusb_transfer_priv(itransfer);
-	struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(transfer->dev_handle);
+	struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(transfer->dev_handle);
 	PWINUSB_SETUP_PACKET setup = (PWINUSB_SETUP_PACKET)transfer->buffer;
-	ULONG size;
+	ULONG size, transferred;
 	HANDLE winusb_handle;
 	OVERLAPPED *overlapped;
 	int current_interface;
@@ -2703,7 +2780,7 @@
 	size = transfer->length - LIBUSB_CONTROL_SETUP_SIZE;
 
 	// Windows places upper limits on the control transfer size
-	// See: https://msdn.microsoft.com/en-us/library/windows/hardware/ff538112.aspx
+	// See: https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/usb-bandwidth-allocation#maximum-transfer-size
 	if (size > MAX_CTRL_BUFFER_LENGTH)
 		return LIBUSB_ERROR_INVALID_PARAM;
 
@@ -2716,8 +2793,9 @@
 			return LIBUSB_ERROR_NOT_FOUND;
 	}
 
-	usbi_dbg("will use interface %d", current_interface);
+	usbi_dbg(ITRANSFER_CTX(itransfer), "will use interface %d", current_interface);
 
+	transfer_priv->interface_number = (uint8_t)current_interface;
 	winusb_handle = handle_priv->interface_handle[current_interface].api_handle;
 	set_transfer_priv_handle(itransfer, handle_priv->interface_handle[current_interface].dev_handle);
 	overlapped = get_transfer_priv_overlapped(itransfer);
@@ -2732,22 +2810,22 @@
 		}
 		windows_force_sync_completion(itransfer, 0);
 	} else {
-		if (!WinUSBX[sub_api].ControlTransfer(winusb_handle, *setup, transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE, size, NULL, overlapped)) {
+		if (!WinUSBX[sub_api].ControlTransfer(winusb_handle, *setup, transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE, size, &transferred, overlapped)) {
 			if (GetLastError() != ERROR_IO_PENDING) {
 				usbi_warn(TRANSFER_CTX(transfer), "ControlTransfer failed: %s", windows_error_str(0));
 				return LIBUSB_ERROR_IO;
 			}
+		} else {
+			windows_force_sync_completion(itransfer, transferred);
 		}
 	}
 
-	transfer_priv->interface_number = (uint8_t)current_interface;
-
 	return LIBUSB_SUCCESS;
 }
 
 static int winusbx_set_interface_altsetting(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface, uint8_t altsetting)
 {
-	struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle);
+	struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle);
 	struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev);
 	HANDLE winusb_handle;
 
@@ -2803,7 +2881,7 @@
 {
 	struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
 	struct winusb_transfer_priv *transfer_priv = get_winusb_transfer_priv(itransfer);
-	struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(transfer->dev_handle);
+	struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(transfer->dev_handle);
 	struct winusb_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev);
 	HANDLE winusb_handle;
 	OVERLAPPED *overlapped;
@@ -2818,8 +2896,9 @@
 		return LIBUSB_ERROR_NOT_FOUND;
 	}
 
-	usbi_dbg("matched endpoint %02X with interface %d", transfer->endpoint, current_interface);
+	usbi_dbg(TRANSFER_CTX(transfer), "matched endpoint %02X with interface %d", transfer->endpoint, current_interface);
 
+	transfer_priv->interface_number = (uint8_t)current_interface;
 	winusb_handle = handle_priv->interface_handle[current_interface].api_handle;
 	set_transfer_priv_handle(itransfer, handle_priv->interface_handle[current_interface].dev_handle);
 	overlapped = get_transfer_priv_overlapped(itransfer);
@@ -2852,10 +2931,10 @@
 		}
 
 		if (IS_XFERIN(transfer)) {
-			usbi_dbg("reading %d iso packets", transfer->num_iso_packets);
+			usbi_dbg(TRANSFER_CTX(transfer), "reading %d iso packets", transfer->num_iso_packets);
 			ret = WinUSBX[sub_api].IsoReadPipe(winusb_handle, transfer->endpoint, transfer->buffer, transfer->length, overlapped, iso_context);
 		} else {
-			usbi_dbg("writing %d iso packets", transfer->num_iso_packets);
+			usbi_dbg(TRANSFER_CTX(transfer), "writing %d iso packets", transfer->num_iso_packets);
 			ret = WinUSBX[sub_api].IsoWritePipe(winusb_handle, transfer->endpoint, transfer->buffer, transfer->length, overlapped, iso_context);
 		}
 
@@ -2864,8 +2943,6 @@
 			return LIBUSB_ERROR_IO;
 		}
 
-		transfer_priv->interface_number = (uint8_t)current_interface;
-
 		return LIBUSB_SUCCESS;
 	} else if (sub_api == SUB_API_WINUSB) {
 		WINUSB_PIPE_INFORMATION_EX pipe_info_ex = { 0 };
@@ -2913,7 +2990,11 @@
 			// WinUSB only supports isoch transfers spanning a full USB frames. Later, we might be smarter about this
 			// and allocate a temporary buffer. However, this is harder than it seems as its destruction would depend on overlapped
 			// IO...
-			iso_transfer_size_multiple = (pipe_info_ex.MaximumBytesPerInterval * 8) / interval;
+			if (transfer->dev_handle->dev->speed >= LIBUSB_SPEED_HIGH) // Microframes (125us)
+				iso_transfer_size_multiple = (pipe_info_ex.MaximumBytesPerInterval * 8) / interval;
+			else // Normal Frames (1ms)
+				iso_transfer_size_multiple = pipe_info_ex.MaximumBytesPerInterval / interval;
+
 			if (transfer->length % iso_transfer_size_multiple != 0) {
 				usbi_err(TRANSFER_CTX(transfer), "length of isoch buffer must be a multiple of the MaximumBytesPerInterval * 8 / Interval");
 				return LIBUSB_ERROR_INVALID_PARAM;
@@ -2980,8 +3061,6 @@
 
 		transfer_priv->isoch_buffer_handle = buffer_handle;
 
-		transfer_priv->interface_number = (uint8_t)current_interface;
-
 		return LIBUSB_SUCCESS;
 	} else {
 		PRINT_UNSUPPORTED_API(winusbx_submit_iso_transfer);
@@ -2993,7 +3072,7 @@
 {
 	struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
 	struct winusb_transfer_priv *transfer_priv = get_winusb_transfer_priv(itransfer);
-	struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(transfer->dev_handle);
+	struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(transfer->dev_handle);
 	struct winusb_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev);
 	HANDLE winusb_handle;
 	OVERLAPPED *overlapped;
@@ -3008,17 +3087,35 @@
 		return LIBUSB_ERROR_NOT_FOUND;
 	}
 
-	usbi_dbg("matched endpoint %02X with interface %d", transfer->endpoint, current_interface);
+	usbi_dbg(TRANSFER_CTX(transfer), "matched endpoint %02X with interface %d", transfer->endpoint, current_interface);
 
+	transfer_priv->interface_number = (uint8_t)current_interface;
 	winusb_handle = handle_priv->interface_handle[current_interface].api_handle;
 	set_transfer_priv_handle(itransfer, handle_priv->interface_handle[current_interface].dev_handle);
 	overlapped = get_transfer_priv_overlapped(itransfer);
 
 	if (IS_XFERIN(transfer)) {
-		usbi_dbg("reading %d bytes", transfer->length);
+		usbi_dbg(TRANSFER_CTX(transfer), "reading %d bytes", transfer->length);
 		ret = WinUSBX[sub_api].ReadPipe(winusb_handle, transfer->endpoint, transfer->buffer, transfer->length, NULL, overlapped);
 	} else {
-		usbi_dbg("writing %d bytes", transfer->length);
+		// Set SHORT_PACKET_TERMINATE if ZLP requested.
+		// Changing this can be a problem with packets in flight, so only allow on the first transfer.
+		UCHAR policy = (transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET) != 0;
+		uint8_t* current_zlp = &handle_priv->interface_handle[current_interface].zlp[transfer->endpoint];
+		if (*current_zlp == WINUSB_ZLP_UNSET) {
+			if (policy &&
+				!WinUSBX[sub_api].SetPipePolicy(winusb_handle, transfer->endpoint,
+				SHORT_PACKET_TERMINATE, sizeof(UCHAR), &policy)) {
+				usbi_err(TRANSFER_CTX(transfer), "failed to set SHORT_PACKET_TERMINATE for endpoint %02X", transfer->endpoint);
+				return LIBUSB_ERROR_NOT_SUPPORTED;
+			}
+			*current_zlp = policy ? WINUSB_ZLP_ON : WINUSB_ZLP_OFF;
+		} else if (policy != (*current_zlp == WINUSB_ZLP_ON)) {
+			usbi_err(TRANSFER_CTX(transfer), "cannot change ZERO_PACKET for endpoint %02X on Windows", transfer->endpoint);
+			return LIBUSB_ERROR_NOT_SUPPORTED;
+		}
+
+		usbi_dbg(TRANSFER_CTX(transfer), "writing %d bytes", transfer->length);
 		ret = WinUSBX[sub_api].WritePipe(winusb_handle, transfer->endpoint, transfer->buffer, transfer->length, NULL, overlapped);
 	}
 
@@ -3027,14 +3124,12 @@
 		return LIBUSB_ERROR_IO;
 	}
 
-	transfer_priv->interface_number = (uint8_t)current_interface;
-
 	return LIBUSB_SUCCESS;
 }
 
 static int winusbx_clear_halt(int sub_api, struct libusb_device_handle *dev_handle, unsigned char endpoint)
 {
-	struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle);
+	struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle);
 	struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev);
 	HANDLE winusb_handle;
 	int current_interface;
@@ -3047,7 +3142,7 @@
 		return LIBUSB_ERROR_NOT_FOUND;
 	}
 
-	usbi_dbg("matched endpoint %02X with interface %d", endpoint, current_interface);
+	usbi_dbg(HANDLE_CTX(dev_handle), "matched endpoint %02X with interface %d", endpoint, current_interface);
 	winusb_handle = handle_priv->interface_handle[current_interface].api_handle;
 
 	if (!WinUSBX[sub_api].ResetPipe(winusb_handle, endpoint)) {
@@ -3061,7 +3156,7 @@
 static int winusbx_cancel_transfer(int sub_api, struct usbi_transfer *itransfer)
 {
 	struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
-	struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(transfer->dev_handle);
+	struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(transfer->dev_handle);
 	struct winusb_transfer_priv *transfer_priv = get_winusb_transfer_priv(itransfer);
 	struct winusb_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev);
 	int current_interface = transfer_priv->interface_number;
@@ -3069,7 +3164,7 @@
 
 	CHECK_WINUSBX_AVAILABLE(sub_api);
 
-	usbi_dbg("will use interface %d", current_interface);
+	usbi_dbg(TRANSFER_CTX(transfer), "will use interface %d", current_interface);
 
 	handle = handle_priv->interface_handle[current_interface].api_handle;
 	if (!WinUSBX[sub_api].AbortPipe(handle, transfer->endpoint)) {
@@ -3091,7 +3186,7 @@
 // TODO: (post hotplug): see if we can force eject the device and redetect it (reuse hotplug?)
 static int winusbx_reset_device(int sub_api, struct libusb_device_handle *dev_handle)
 {
-	struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle);
+	struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle);
 	struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev);
 	HANDLE winusb_handle;
 	int i, j;
@@ -3103,7 +3198,7 @@
 		winusb_handle = handle_priv->interface_handle[i].api_handle;
 		if (HANDLE_VALID(winusb_handle)) {
 			for (j = 0; j < priv->usb_interface[i].nb_endpoints; j++) {
-				usbi_dbg("resetting ep %02X", priv->usb_interface[i].endpoint[j]);
+				usbi_dbg(HANDLE_CTX(dev_handle), "resetting ep %02X", priv->usb_interface[i].endpoint[j]);
 				if (!WinUSBX[sub_api].AbortPipe(winusb_handle, priv->usb_interface[i].endpoint[j]))
 					usbi_err(HANDLE_CTX(dev_handle), "AbortPipe (pipe address %02X) failed: %s",
 						priv->usb_interface[i].endpoint[j], windows_error_str(0));
@@ -3138,12 +3233,25 @@
 	int i;
 
 	if (transfer->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) {
+		struct winusb_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev);
+
+		if (sub_api == SUB_API_NOTSET)
+			sub_api = priv->sub_api;
+		if (WinUSBX[sub_api].hDll == NULL)
+			return LIBUSB_TRANSFER_ERROR;
+
 		// for isochronous, need to copy the individual iso packet actual_lengths and statuses
 		if ((sub_api == SUB_API_LIBUSBK) || (sub_api == SUB_API_LIBUSB0)) {
 			// iso only supported on libusbk-based backends for now
 			PKISO_CONTEXT iso_context = transfer_priv->iso_context;
 			for (i = 0; i < transfer->num_iso_packets; i++) {
-				transfer->iso_packet_desc[i].actual_length = iso_context->IsoPackets[i].actual_length;
+				if (IS_XFERIN(transfer)) {
+					transfer->iso_packet_desc[i].actual_length = iso_context->IsoPackets[i].actual_length;
+				} else {
+					// On Windows the usbd Length field is not used for OUT transfers.
+					// Copy the requested value back for consistency with other platforms.
+					transfer->iso_packet_desc[i].actual_length = transfer->iso_packet_desc[i].length;
+				}
 				// TODO translate USDB_STATUS codes http://msdn.microsoft.com/en-us/library/ff539136(VS.85).aspx to libusb_transfer_status
 				//transfer->iso_packet_desc[i].status = transfer_priv->iso_context->IsoPackets[i].status;
 			}
@@ -3164,6 +3272,9 @@
 			} else {
 				for (i = 0; i < transfer->num_iso_packets; i++) {
 					transfer->iso_packet_desc[i].status = LIBUSB_TRANSFER_COMPLETED;
+					// On Windows the usbd Length field is not used for OUT transfers.
+					// Copy the requested value back for consistency with other platforms.
+					transfer->iso_packet_desc[i].actual_length = transfer->iso_packet_desc[i].length;
 				}
 			}
 		} else {
@@ -3439,28 +3550,28 @@
 
 	switch (type) {
 	case LIBUSB_DT_DEVICE:
-		usbi_dbg("LIBUSB_DT_DEVICE");
+		usbi_dbg(DEVICE_CTX(dev), "LIBUSB_DT_DEVICE");
 		return _hid_get_device_descriptor(priv->hid, data, size);
 	case LIBUSB_DT_CONFIG:
-		usbi_dbg("LIBUSB_DT_CONFIG");
+		usbi_dbg(DEVICE_CTX(dev), "LIBUSB_DT_CONFIG");
 		if (!_index)
 			return _hid_get_config_descriptor(priv->hid, data, size);
 		return LIBUSB_ERROR_INVALID_PARAM;
 	case LIBUSB_DT_STRING:
-		usbi_dbg("LIBUSB_DT_STRING");
+		usbi_dbg(DEVICE_CTX(dev), "LIBUSB_DT_STRING");
 		return _hid_get_string_descriptor(priv->hid, _index, data, size, hid_handle);
 	case LIBUSB_DT_HID:
-		usbi_dbg("LIBUSB_DT_HID");
+		usbi_dbg(DEVICE_CTX(dev), "LIBUSB_DT_HID");
 		if (!_index)
 			return _hid_get_hid_descriptor(priv->hid, data, size);
 		return LIBUSB_ERROR_INVALID_PARAM;
 	case LIBUSB_DT_REPORT:
-		usbi_dbg("LIBUSB_DT_REPORT");
+		usbi_dbg(DEVICE_CTX(dev), "LIBUSB_DT_REPORT");
 		if (!_index)
 			return _hid_get_report_descriptor(priv->hid, data, size);
 		return LIBUSB_ERROR_INVALID_PARAM;
 	case LIBUSB_DT_PHYSICAL:
-		usbi_dbg("LIBUSB_DT_PHYSICAL");
+		usbi_dbg(DEVICE_CTX(dev), "LIBUSB_DT_PHYSICAL");
 		if (HidD_GetPhysicalDescriptor(hid_handle, data, (ULONG)*size))
 			return LIBUSB_COMPLETED;
 		return LIBUSB_ERROR_OTHER;
@@ -3502,7 +3613,7 @@
 		return LIBUSB_ERROR_NO_MEM;
 
 	buf[0] = (uint8_t)id; // Must be set always
-	usbi_dbg("report ID: 0x%02X", buf[0]);
+	usbi_dbg(DEVICE_CTX(dev), "report ID: 0x%02X", buf[0]);
 
 	// NB: The size returned by DeviceIoControl doesn't include report IDs when not in use (0)
 	if (!DeviceIoControl(hid_handle, ioctl_code, buf, expected_size + 1,
@@ -3550,7 +3661,7 @@
 		return LIBUSB_ERROR_INVALID_PARAM;
 	}
 
-	usbi_dbg("report ID: 0x%02X", id);
+	usbi_dbg(DEVICE_CTX(dev), "report ID: 0x%02X", id);
 	// When report IDs are not used (i.e. when id == 0), we must add
 	// a null report ID. Otherwise, we just use original data buffer
 	if (id == 0)
@@ -3644,7 +3755,7 @@
 {
 	struct libusb_device *dev = dev_handle->dev;
 	struct winusb_device_priv *priv = usbi_get_device_priv(dev);
-	struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle);
+	struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle);
 	HIDD_ATTRIBUTES hid_attributes;
 	PHIDP_PREPARSED_DATA preparsed_data = NULL;
 	HIDP_CAPS capabilities;
@@ -3669,7 +3780,7 @@
 	for (i = 0; i < USB_MAXINTERFACES; i++) {
 		if ((priv->usb_interface[i].path != NULL)
 				&& (priv->usb_interface[i].apib->id == USB_API_HID)) {
-			hid_handle = windows_open(dev, priv->usb_interface[i].path, GENERIC_READ | GENERIC_WRITE);
+			hid_handle = windows_open(dev_handle, priv->usb_interface[i].path, GENERIC_READ | GENERIC_WRITE);
 			/*
 			 * http://www.lvr.com/hidfaq.htm: Why do I receive "Access denied" when attempting to access my HID?
 			 * "Windows 2000 and later have exclusive read/write access to HIDs that are configured as a system
@@ -3679,7 +3790,7 @@
 			 */
 			if (hid_handle == INVALID_HANDLE_VALUE) {
 				usbi_warn(HANDLE_CTX(dev_handle), "could not open HID device in R/W mode (keyboard or mouse?) - trying without");
-				hid_handle = windows_open(dev, priv->usb_interface[i].path, 0);
+				hid_handle = windows_open(dev_handle, priv->usb_interface[i].path, 0);
 				if (hid_handle == INVALID_HANDLE_VALUE) {
 					usbi_err(HANDLE_CTX(dev_handle), "could not open device %s (interface %d): %s", priv->path, i, windows_error_str(0));
 					switch (GetLastError()) {
@@ -3709,7 +3820,7 @@
 
 		// Set the maximum available input buffer size
 		for (i = 32; HidD_SetNumInputBuffers(hid_handle, i); i *= 2);
-		usbi_dbg("set maximum input buffer size to %d", i / 2);
+		usbi_dbg(HANDLE_CTX(dev_handle), "set maximum input buffer size to %d", i / 2);
 
 		// Get the maximum input and output report size
 		if (!HidD_GetPreparsedData(hid_handle, &preparsed_data) || !preparsed_data) {
@@ -3726,7 +3837,7 @@
 		size[1] = capabilities.NumberOutputValueCaps;
 		size[2] = capabilities.NumberFeatureValueCaps;
 		for (j = HidP_Input; j <= HidP_Feature; j++) {
-			usbi_dbg("%lu HID %s report value(s) found", ULONG_CAST(size[j]), type[j]);
+			usbi_dbg(HANDLE_CTX(dev_handle), "%lu HID %s report value(s) found", ULONG_CAST(size[j]), type[j]);
 			priv->hid->uses_report_ids[j] = false;
 			if (size[j] > 0) {
 				value_caps = calloc(size[j], sizeof(HIDP_VALUE_CAPS));
@@ -3736,7 +3847,7 @@
 					nb_ids[0] = 0;
 					nb_ids[1] = 0;
 					for (i = 0; i < (int)size[j]; i++) {
-						usbi_dbg("  Report ID: 0x%02X", value_caps[i].ReportID);
+						usbi_dbg(HANDLE_CTX(dev_handle), "  Report ID: 0x%02X", value_caps[i].ReportID);
 						if (value_caps[i].ReportID != 0)
 							nb_ids[1]++;
 						else
@@ -3773,7 +3884,10 @@
 
 		priv->hid->string_index[1] = dev->device_descriptor.iProduct;
 		if (priv->hid->string_index[1] != 0)
-			HidD_GetProductString(hid_handle, priv->hid->string[1], sizeof(priv->hid->string[1]));
+			// Using HidD_GetIndexedString() instead of HidD_GetProductString(), as the latter would otherwise return the name
+			// of the interface instead of the iProduct string whenever the iInterface member of the USB_INTERFACE_DESCRIPTOR
+			// structure for the interface is nonzero (see Remarks section in the documentation of the HID API routines)
+			HidD_GetIndexedString(hid_handle, priv->hid->string_index[1], priv->hid->string[1], sizeof(priv->hid->string[1]));
 		else
 			priv->hid->string[1][0] = 0;
 
@@ -3793,7 +3907,7 @@
 static void hid_close(int sub_api, struct libusb_device_handle *dev_handle)
 {
 	struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev);
-	struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle);
+	struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle);
 	HANDLE file_handle;
 	int i;
 
@@ -3813,7 +3927,7 @@
 
 static int hid_claim_interface(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface)
 {
-	struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle);
+	struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle);
 	struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev);
 
 	UNUSED(sub_api);
@@ -3829,7 +3943,7 @@
 
 	handle_priv->interface_handle[iface].dev_handle = INTERFACE_CLAIMED;
 
-	usbi_dbg("claimed interface %u", iface);
+	usbi_dbg(HANDLE_CTX(dev_handle), "claimed interface %u", iface);
 	handle_priv->active_interface = iface;
 
 	return LIBUSB_SUCCESS;
@@ -3837,7 +3951,7 @@
 
 static int hid_release_interface(int sub_api, struct libusb_device_handle *dev_handle, uint8_t iface)
 {
-	struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle);
+	struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle);
 	struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev);
 
 	UNUSED(sub_api);
@@ -3874,7 +3988,7 @@
 	struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
 	struct winusb_transfer_priv *transfer_priv = get_winusb_transfer_priv(itransfer);
 	struct libusb_device_handle *dev_handle = transfer->dev_handle;
-	struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle);
+	struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle);
 	struct winusb_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev);
 	WINUSB_SETUP_PACKET *setup = (WINUSB_SETUP_PACKET *)transfer->buffer;
 	HANDLE hid_handle;
@@ -3900,8 +4014,9 @@
 			return LIBUSB_ERROR_NOT_FOUND;
 	}
 
-	usbi_dbg("will use interface %d", current_interface);
+	usbi_dbg(ITRANSFER_CTX(itransfer), "will use interface %d", current_interface);
 
+	transfer_priv->interface_number = (uint8_t)current_interface;
 	hid_handle = handle_priv->interface_handle[current_interface].api_handle;
 	set_transfer_priv_handle(itransfer, hid_handle);
 	overlapped = get_transfer_priv_overlapped(itransfer);
@@ -3963,8 +4078,6 @@
 		r = LIBUSB_SUCCESS;
 	}
 
-	transfer_priv->interface_number = (uint8_t)current_interface;
-
 	return LIBUSB_SUCCESS;
 }
 
@@ -3972,7 +4085,7 @@
 {
 	struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
 	struct winusb_transfer_priv *transfer_priv = get_winusb_transfer_priv(itransfer);
-	struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(transfer->dev_handle);
+	struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(transfer->dev_handle);
 	struct winusb_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev);
 	HANDLE hid_handle;
 	OVERLAPPED *overlapped;
@@ -3983,6 +4096,9 @@
 	UNUSED(sub_api);
 	CHECK_HID_AVAILABLE;
 
+	if (IS_XFEROUT(transfer) && (transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET))
+		return LIBUSB_ERROR_NOT_SUPPORTED;
+
 	transfer_priv->hid_dest = NULL;
 	safe_free(transfer_priv->hid_buffer);
 
@@ -3992,8 +4108,9 @@
 		return LIBUSB_ERROR_NOT_FOUND;
 	}
 
-	usbi_dbg("matched endpoint %02X with interface %d", transfer->endpoint, current_interface);
+	usbi_dbg(TRANSFER_CTX(transfer), "matched endpoint %02X with interface %d", transfer->endpoint, current_interface);
 
+	transfer_priv->interface_number = (uint8_t)current_interface;
 	hid_handle = handle_priv->interface_handle[current_interface].api_handle;
 	set_transfer_priv_handle(itransfer, hid_handle);
 	overlapped = get_transfer_priv_overlapped(itransfer);
@@ -4015,7 +4132,7 @@
 
 	if (direction_in) {
 		transfer_priv->hid_dest = transfer->buffer;
-		usbi_dbg("reading %d bytes (report ID: 0x00)", length);
+		usbi_dbg(TRANSFER_CTX(transfer), "reading %d bytes (report ID: 0x00)", length);
 		ret = ReadFile(hid_handle, transfer_priv->hid_buffer, length + 1, NULL, overlapped);
 	} else {
 		if (!priv->hid->uses_report_ids[1])
@@ -4024,7 +4141,7 @@
 			// We could actually do without the calloc and memcpy in this case
 			memcpy(transfer_priv->hid_buffer, transfer->buffer, transfer->length);
 
-		usbi_dbg("writing %d bytes (report ID: 0x%02X)", length, transfer_priv->hid_buffer[0]);
+		usbi_dbg(TRANSFER_CTX(transfer), "writing %d bytes (report ID: 0x%02X)", length, transfer_priv->hid_buffer[0]);
 		ret = WriteFile(hid_handle, transfer_priv->hid_buffer, length, NULL, overlapped);
 	}
 
@@ -4034,14 +4151,12 @@
 		return LIBUSB_ERROR_IO;
 	}
 
-	transfer_priv->interface_number = (uint8_t)current_interface;
-
 	return LIBUSB_SUCCESS;
 }
 
 static int hid_reset_device(int sub_api, struct libusb_device_handle *dev_handle)
 {
-	struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle);
+	struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle);
 	HANDLE hid_handle;
 	int current_interface;
 
@@ -4060,7 +4175,7 @@
 
 static int hid_clear_halt(int sub_api, struct libusb_device_handle *dev_handle, unsigned char endpoint)
 {
-	struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle);
+	struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle);
 	struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev);
 	HANDLE hid_handle;
 	int current_interface;
@@ -4074,7 +4189,7 @@
 		return LIBUSB_ERROR_NOT_FOUND;
 	}
 
-	usbi_dbg("matched endpoint %02X with interface %d", endpoint, current_interface);
+	usbi_dbg(HANDLE_CTX(dev_handle), "matched endpoint %02X with interface %d", endpoint, current_interface);
 	hid_handle = handle_priv->interface_handle[current_interface].api_handle;
 
 	// No endpoint selection with Microsoft's implementation, so we try to flush the
@@ -4109,8 +4224,6 @@
 				}
 
 				if (transfer_priv->hid_buffer[0] == 0) {
-					// Discard the 1 byte report ID prefix
-					length--;
 					memcpy(transfer_priv->hid_dest, transfer_priv->hid_buffer + 1, length);
 				} else {
 					memcpy(transfer_priv->hid_dest, transfer_priv->hid_buffer, length);
@@ -4171,7 +4284,7 @@
 		// open HID devices with a U2F usage unless running as administrator. We ignore this
 		// failure and proceed without the HID device opened.
 		if (r == LIBUSB_ERROR_ACCESS) {
-			usbi_dbg("ignoring access denied error while opening HID interface of composite device");
+			usbi_dbg(HANDLE_CTX(dev_handle), "ignoring access denied error while opening HID interface of composite device");
 			r = LIBUSB_SUCCESS;
 		}
 	}
@@ -4280,7 +4393,7 @@
 
 	// Try and target a specific interface if the control setup indicates such
 	if ((iface >= 0) && (iface < USB_MAXINTERFACES)) {
-		usbi_dbg("attempting control transfer targeted to interface %d", iface);
+		usbi_dbg(TRANSFER_CTX(transfer), "attempting control transfer targeted to interface %d", iface);
 		if ((priv->usb_interface[iface].path != NULL)
 				&& (priv->usb_interface[iface].apib->submit_control_transfer != NULL)) {
 			r = priv->usb_interface[iface].apib->submit_control_transfer(priv->usb_interface[iface].sub_api, itransfer);
@@ -4296,10 +4409,10 @@
 			if ((priv->usb_interface[iface].path != NULL)
 					&& (priv->usb_interface[iface].apib->submit_control_transfer != NULL)) {
 				if ((pass == 0) && (priv->usb_interface[iface].restricted_functionality)) {
-					usbi_dbg("trying to skip restricted interface #%d (HID keyboard or mouse?)", iface);
+					usbi_dbg(TRANSFER_CTX(transfer), "trying to skip restricted interface #%d (HID keyboard or mouse?)", iface);
 					continue;
 				}
-				usbi_dbg("using interface %d", iface);
+				usbi_dbg(TRANSFER_CTX(transfer), "using interface %d", iface);
 				r = priv->usb_interface[iface].apib->submit_control_transfer(priv->usb_interface[iface].sub_api, itransfer);
 				// If not supported on this API, it may be supported on another, so don't give up yet!!
 				if (r == LIBUSB_ERROR_NOT_SUPPORTED)
@@ -4316,7 +4429,7 @@
 static int composite_submit_bulk_transfer(int sub_api, struct usbi_transfer *itransfer)
 {
 	struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
-	struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(transfer->dev_handle);
+	struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(transfer->dev_handle);
 	struct winusb_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev);
 	int current_interface;
 
@@ -4337,7 +4450,7 @@
 static int composite_submit_iso_transfer(int sub_api, struct usbi_transfer *itransfer)
 {
 	struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
-	struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(transfer->dev_handle);
+	struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(transfer->dev_handle);
 	struct winusb_device_priv *priv = usbi_get_device_priv(transfer->dev_handle->dev);
 	int current_interface;
 
@@ -4357,7 +4470,7 @@
 
 static int composite_clear_halt(int sub_api, struct libusb_device_handle *dev_handle, unsigned char endpoint)
 {
-	struct winusb_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle);
+	struct winusb_device_handle_priv *handle_priv = get_winusb_device_handle_priv(dev_handle);
 	struct winusb_device_priv *priv = usbi_get_device_priv(dev_handle->dev);
 	int current_interface;
 
diff --git a/libusb/os/windows_winusb.h b/libusb/os/windows_winusb.h
index 49355d4..6afd5cb 100644
--- a/libusb/os/windows_winusb.h
+++ b/libusb/os/windows_winusb.h
@@ -23,21 +23,11 @@
 #ifndef LIBUSB_WINDOWS_WINUSB_H
 #define LIBUSB_WINDOWS_WINUSB_H
 
+#include <devioctl.h>
+#include <guiddef.h>
+
 #include "windows_common.h"
 
-#if defined(_MSC_VER)
-// disable /W4 MSVC warnings that are benign
-#pragma warning(disable:4214)  // bit field types other than int
-#endif
-
-// Missing from MSVC6 setupapi.h
-#ifndef SPDRP_ADDRESS
-#define SPDRP_ADDRESS		28
-#endif
-#ifndef SPDRP_INSTALL_STATE
-#define SPDRP_INSTALL_STATE	34
-#endif
-
 #define MAX_CTRL_BUFFER_LENGTH	4096
 #define MAX_USB_STRING_LENGTH	128
 #define MAX_HID_REPORT_SIZE	1024
@@ -55,18 +45,10 @@
 // http://msdn.microsoft.com/en-us/library/ff545978.aspx
 // http://msdn.microsoft.com/en-us/library/ff545972.aspx
 // http://msdn.microsoft.com/en-us/library/ff545982.aspx
-#ifndef GUID_DEVINTERFACE_USB_HOST_CONTROLLER
-const GUID GUID_DEVINTERFACE_USB_HOST_CONTROLLER = {0x3ABF6F2D, 0x71C4, 0x462A, {0x8A, 0x92, 0x1E, 0x68, 0x61, 0xE6, 0xAF, 0x27}};
-#endif
-#ifndef GUID_DEVINTERFACE_USB_DEVICE
-const GUID GUID_DEVINTERFACE_USB_DEVICE = {0xA5DCBF10, 0x6530, 0x11D2, {0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED}};
-#endif
-#ifndef GUID_DEVINTERFACE_USB_HUB
-const GUID GUID_DEVINTERFACE_USB_HUB = {0xF18A0E88, 0xC30C, 0x11D0, {0x88, 0x15, 0x00, 0xA0, 0xC9, 0x06, 0xBE, 0xD8}};
-#endif
-#ifndef GUID_DEVINTERFACE_LIBUSB0_FILTER
-const GUID GUID_DEVINTERFACE_LIBUSB0_FILTER = {0xF9F3FF14, 0xAE21, 0x48A0, {0x8A, 0x25, 0x80, 0x11, 0xA7, 0xA9, 0x31, 0xD9}};
-#endif
+static const GUID GUID_DEVINTERFACE_USB_HOST_CONTROLLER = {0x3ABF6F2D, 0x71C4, 0x462A, {0x8A, 0x92, 0x1E, 0x68, 0x61, 0xE6, 0xAF, 0x27}};
+static const GUID GUID_DEVINTERFACE_USB_HUB = {0xF18A0E88, 0xC30C, 0x11D0, {0x88, 0x15, 0x00, 0xA0, 0xC9, 0x06, 0xBE, 0xD8}};
+static const GUID GUID_DEVINTERFACE_USB_DEVICE = {0xA5DCBF10, 0x6530, 0x11D2, {0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED}};
+static const GUID GUID_DEVINTERFACE_LIBUSB0_FILTER = {0xF9F3FF14, 0xAE21, 0x48A0, {0x8A, 0x25, 0x80, 0x11, 0xA7, 0xA9, 0x31, 0xD9}};
 
 // The following define MUST be == sizeof(USB_DESCRIPTOR_REQUEST)
 #define USB_DESCRIPTOR_REQUEST_SIZE	12U
@@ -114,7 +96,7 @@
 extern const struct windows_usb_api_backend usb_api_backend[USB_API_MAX];
 
 #define PRINT_UNSUPPORTED_API(fname)				\
-	usbi_dbg("unsupported API call for '%s' "		\
+	usbi_dbg(NULL, "unsupported API call for '%s' "		\
 		"(unrecognized device driver)", #fname)
 
 #define CHECK_SUPPORTED_API(apip, fname)			\
@@ -152,11 +134,6 @@
 #define LIBUSB_REQ_IN(request_type)		((request_type) & LIBUSB_ENDPOINT_IN)
 #define LIBUSB_REQ_OUT(request_type)		(!LIBUSB_REQ_IN(request_type))
 
-#ifndef CTL_CODE
-#define CTL_CODE(DeviceType, Function, Method, Access) \
-	(((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
-#endif
-
 // The following are used for HID reports IOCTLs
 #define HID_IN_CTL_CODE(id) \
 	CTL_CODE(FILE_DEVICE_KEYBOARD, (id), METHOD_IN_DIRECT, FILE_ANY_ACCESS)
@@ -259,13 +236,9 @@
 
 /* AdvAPI32 dependencies */
 DLL_DECLARE_HANDLE(AdvAPI32);
-DLL_DECLARE_FUNC_PREFIXED(WINAPI, LONG, p, RegQueryValueExW, (HKEY, LPCWSTR, LPDWORD, LPDWORD, LPBYTE, LPDWORD));
+DLL_DECLARE_FUNC_PREFIXED(WINAPI, LONG, p, RegQueryValueExA, (HKEY, LPCSTR, LPDWORD, LPDWORD, LPBYTE, LPDWORD));
 DLL_DECLARE_FUNC_PREFIXED(WINAPI, LONG, p, RegCloseKey, (HKEY));
 
-/* OLE32 dependency */
-DLL_DECLARE_HANDLE(OLE32);
-DLL_DECLARE_FUNC_PREFIXED(WINAPI, HRESULT, p, IIDFromString, (LPCOLESTR, LPIID));
-
 /* SetupAPI dependencies */
 DLL_DECLARE_HANDLE(SetupAPI);
 DLL_DECLARE_FUNC_PREFIXED(WINAPI, HDEVINFO, p, SetupDiGetClassDevsA, (LPCGUID, PCSTR, HWND, DWORD));
@@ -282,23 +255,12 @@
 DLL_DECLARE_FUNC_PREFIXED(WINAPI, HKEY, p, SetupDiOpenDevRegKey, (HDEVINFO, PSP_DEVINFO_DATA, DWORD, DWORD, DWORD, REGSAM));
 DLL_DECLARE_FUNC_PREFIXED(WINAPI, HKEY, p, SetupDiOpenDeviceInterfaceRegKey, (HDEVINFO, PSP_DEVICE_INTERFACE_DATA, DWORD, DWORD));
 
+#define FILE_DEVICE_USB	FILE_DEVICE_UNKNOWN
 
-#ifndef USB_GET_NODE_INFORMATION
 #define USB_GET_NODE_INFORMATION			258
-#endif
-#ifndef USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION
 #define USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION		260
-#endif
-#ifndef USB_GET_NODE_CONNECTION_INFORMATION_EX
 #define USB_GET_NODE_CONNECTION_INFORMATION_EX		274
-#endif
-#ifndef USB_GET_NODE_CONNECTION_INFORMATION_EX_V2
 #define USB_GET_NODE_CONNECTION_INFORMATION_EX_V2	279
-#endif
-
-#ifndef FILE_DEVICE_USB
-#define FILE_DEVICE_USB		FILE_DEVICE_UNKNOWN
-#endif
 
 #define USB_CTL_CODE(id) \
 	CTL_CODE(FILE_DEVICE_USB, (id), METHOD_BUFFERED, FILE_ANY_ACCESS)
@@ -342,6 +304,12 @@
 	UsbMIParent
 } USB_HUB_NODE;
 
+#if defined(_MSC_VER)
+// disable /W4 MSVC warnings that are benign
+#pragma warning(push)
+#pragma warning(disable:4214)  // bit field types other than int
+#endif
+
 // Most of the structures below need to be packed
 #include <pshpack1.h>
 
@@ -439,6 +407,11 @@
 
 #include <poppack.h>
 
+#if defined(_MSC_VER)
+// Restore original warnings
+#pragma warning(pop)
+#endif
+
 /* winusb.dll interface */
 
 /* pipe policies */
diff --git a/libusb/sync.c b/libusb/sync.c
index adc95b4..1fa1f0b 100644
--- a/libusb/sync.c
+++ b/libusb/sync.c
@@ -36,7 +36,7 @@
 {
 	int *completed = transfer->user_data;
 	*completed = 1;
-	usbi_dbg("actual_length=%d", transfer->actual_length);
+	usbi_dbg(TRANSFER_CTX(transfer), "actual_length=%d", transfer->actual_length);
 	/* caller interprets result and frees transfer */
 }
 
diff --git a/libusb/version.h b/libusb/version.h
index d8ebde4..fe95d84 100644
--- a/libusb/version.h
+++ b/libusb/version.h
@@ -7,7 +7,7 @@
 #define LIBUSB_MINOR 0
 #endif
 #ifndef LIBUSB_MICRO
-#define LIBUSB_MICRO 24
+#define LIBUSB_MICRO 26
 #endif
 #ifndef LIBUSB_NANO
 #define LIBUSB_NANO 0
diff --git a/libusb/version_nano.h b/libusb/version_nano.h
index 0f100a8..dbd5d5f 100644
--- a/libusb/version_nano.h
+++ b/libusb/version_nano.h
@@ -1 +1 @@
-#define LIBUSB_NANO 11584
+#define LIBUSB_NANO 11724
diff --git a/msvc/libusb_dll_2013.vcxproj b/msvc/libusb_dll_2013.vcxproj
index 56ffd75..03212dc 100644
--- a/msvc/libusb_dll_2013.vcxproj
+++ b/msvc/libusb_dll_2013.vcxproj
@@ -83,7 +83,6 @@
   <ItemGroup>
     <ClInclude Include=".\config.h" />
     <ClInclude Include="..\libusb\os\events_windows.h" />
-    <ClInclude Include="..\libusb\hotplug.h" />
     <ClInclude Include="..\libusb\libusb.h" />
     <ClInclude Include="..\libusb\libusbi.h" />
     <ClInclude Include="..\libusb\os\threads_windows.h" />
diff --git a/msvc/libusb_dll_2013.vcxproj.filters b/msvc/libusb_dll_2013.vcxproj.filters
index 8da28e3..c8643f2 100644
--- a/msvc/libusb_dll_2013.vcxproj.filters
+++ b/msvc/libusb_dll_2013.vcxproj.filters
@@ -21,9 +21,6 @@
     <ClInclude Include="..\libusb\os\events_windows.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="..\libusb\hotplug.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
     <ClInclude Include="..\libusb\libusb.h">
       <Filter>Header Files</Filter>
     </ClInclude>
diff --git a/msvc/libusb_dll_2015.vcxproj b/msvc/libusb_dll_2015.vcxproj
index d2c850d..f24d94b 100644
--- a/msvc/libusb_dll_2015.vcxproj
+++ b/msvc/libusb_dll_2015.vcxproj
@@ -84,7 +84,6 @@
   <ItemGroup>
     <ClInclude Include=".\config.h" />
     <ClInclude Include="..\libusb\os\events_windows.h" />
-    <ClInclude Include="..\libusb\hotplug.h" />
     <ClInclude Include="..\libusb\libusb.h" />
     <ClInclude Include="..\libusb\libusbi.h" />
     <ClInclude Include="..\libusb\os\threads_windows.h" />
diff --git a/msvc/libusb_dll_2015.vcxproj.filters b/msvc/libusb_dll_2015.vcxproj.filters
index 8da28e3..c8643f2 100644
--- a/msvc/libusb_dll_2015.vcxproj.filters
+++ b/msvc/libusb_dll_2015.vcxproj.filters
@@ -21,9 +21,6 @@
     <ClInclude Include="..\libusb\os\events_windows.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="..\libusb\hotplug.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
     <ClInclude Include="..\libusb\libusb.h">
       <Filter>Header Files</Filter>
     </ClInclude>
diff --git a/msvc/libusb_dll_2017.vcxproj b/msvc/libusb_dll_2017.vcxproj
index 598159d..2ff2f94 100644
--- a/msvc/libusb_dll_2017.vcxproj
+++ b/msvc/libusb_dll_2017.vcxproj
@@ -103,7 +103,6 @@
   <ItemGroup>
     <ClInclude Include=".\config.h" />
     <ClInclude Include="..\libusb\os\events_windows.h" />
-    <ClInclude Include="..\libusb\hotplug.h" />
     <ClInclude Include="..\libusb\libusb.h" />
     <ClInclude Include="..\libusb\libusbi.h" />
     <ClInclude Include="..\libusb\os\threads_windows.h" />
diff --git a/msvc/libusb_dll_2017.vcxproj.filters b/msvc/libusb_dll_2017.vcxproj.filters
index 8da28e3..c8643f2 100644
--- a/msvc/libusb_dll_2017.vcxproj.filters
+++ b/msvc/libusb_dll_2017.vcxproj.filters
@@ -21,9 +21,6 @@
     <ClInclude Include="..\libusb\os\events_windows.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="..\libusb\hotplug.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
     <ClInclude Include="..\libusb\libusb.h">
       <Filter>Header Files</Filter>
     </ClInclude>
diff --git a/msvc/libusb_dll_2019.vcxproj b/msvc/libusb_dll_2019.vcxproj
index dbd8717..266166e 100644
--- a/msvc/libusb_dll_2019.vcxproj
+++ b/msvc/libusb_dll_2019.vcxproj
@@ -103,7 +103,6 @@
   <ItemGroup>
     <ClInclude Include=".\config.h" />
     <ClInclude Include="..\libusb\os\events_windows.h" />
-    <ClInclude Include="..\libusb\hotplug.h" />
     <ClInclude Include="..\libusb\libusb.h" />
     <ClInclude Include="..\libusb\libusbi.h" />
     <ClInclude Include="..\libusb\os\threads_windows.h" />
diff --git a/msvc/libusb_dll_2019.vcxproj.filters b/msvc/libusb_dll_2019.vcxproj.filters
index 8da28e3..c8643f2 100644
--- a/msvc/libusb_dll_2019.vcxproj.filters
+++ b/msvc/libusb_dll_2019.vcxproj.filters
@@ -21,9 +21,6 @@
     <ClInclude Include="..\libusb\os\events_windows.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="..\libusb\hotplug.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
     <ClInclude Include="..\libusb\libusb.h">
       <Filter>Header Files</Filter>
     </ClInclude>
diff --git a/msvc/libusb_static_2013.vcxproj b/msvc/libusb_static_2013.vcxproj
index 1b287e5..94ba597 100644
--- a/msvc/libusb_static_2013.vcxproj
+++ b/msvc/libusb_static_2013.vcxproj
@@ -79,7 +79,6 @@
   <ItemGroup>
     <ClInclude Include=".\config.h" />
     <ClInclude Include="..\libusb\os\events_windows.h" />
-    <ClInclude Include="..\libusb\hotplug.h" />
     <ClInclude Include="..\libusb\libusb.h" />
     <ClInclude Include="..\libusb\libusbi.h" />
     <ClInclude Include="..\libusb\os\threads_windows.h" />
diff --git a/msvc/libusb_static_2013.vcxproj.filters b/msvc/libusb_static_2013.vcxproj.filters
index 2994ca1..a3294da 100644
--- a/msvc/libusb_static_2013.vcxproj.filters
+++ b/msvc/libusb_static_2013.vcxproj.filters
@@ -17,9 +17,6 @@
     <ClInclude Include="..\libusb\os\events_windows.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="..\libusb\hotplug.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
     <ClInclude Include="..\libusb\libusb.h">
       <Filter>Header Files</Filter>
     </ClInclude>
diff --git a/msvc/libusb_static_2015.vcxproj b/msvc/libusb_static_2015.vcxproj
index 9fa30da..f951523 100644
--- a/msvc/libusb_static_2015.vcxproj
+++ b/msvc/libusb_static_2015.vcxproj
@@ -80,7 +80,6 @@
   <ItemGroup>
     <ClInclude Include=".\config.h" />
     <ClInclude Include="..\libusb\os\events_windows.h" />
-    <ClInclude Include="..\libusb\hotplug.h" />
     <ClInclude Include="..\libusb\libusb.h" />
     <ClInclude Include="..\libusb\libusbi.h" />
     <ClInclude Include="..\libusb\os\threads_windows.h" />
diff --git a/msvc/libusb_static_2015.vcxproj.filters b/msvc/libusb_static_2015.vcxproj.filters
index 2994ca1..a3294da 100644
--- a/msvc/libusb_static_2015.vcxproj.filters
+++ b/msvc/libusb_static_2015.vcxproj.filters
@@ -17,9 +17,6 @@
     <ClInclude Include="..\libusb\os\events_windows.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="..\libusb\hotplug.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
     <ClInclude Include="..\libusb\libusb.h">
       <Filter>Header Files</Filter>
     </ClInclude>
diff --git a/msvc/libusb_static_2017.vcxproj b/msvc/libusb_static_2017.vcxproj
index 62076e0..857ee3f 100644
--- a/msvc/libusb_static_2017.vcxproj
+++ b/msvc/libusb_static_2017.vcxproj
@@ -99,7 +99,6 @@
   <ItemGroup>
     <ClInclude Include=".\config.h" />
     <ClInclude Include="..\libusb\os\events_windows.h" />
-    <ClInclude Include="..\libusb\hotplug.h" />
     <ClInclude Include="..\libusb\libusb.h" />
     <ClInclude Include="..\libusb\libusbi.h" />
     <ClInclude Include="..\libusb\os\threads_windows.h" />
diff --git a/msvc/libusb_static_2017.vcxproj.filters b/msvc/libusb_static_2017.vcxproj.filters
index 2994ca1..a3294da 100644
--- a/msvc/libusb_static_2017.vcxproj.filters
+++ b/msvc/libusb_static_2017.vcxproj.filters
@@ -17,9 +17,6 @@
     <ClInclude Include="..\libusb\os\events_windows.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="..\libusb\hotplug.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
     <ClInclude Include="..\libusb\libusb.h">
       <Filter>Header Files</Filter>
     </ClInclude>
diff --git a/msvc/libusb_static_2019.vcxproj b/msvc/libusb_static_2019.vcxproj
index 60ad642..036ce95 100644
--- a/msvc/libusb_static_2019.vcxproj
+++ b/msvc/libusb_static_2019.vcxproj
@@ -99,7 +99,6 @@
   <ItemGroup>
     <ClInclude Include=".\config.h" />
     <ClInclude Include="..\libusb\os\events_windows.h" />
-    <ClInclude Include="..\libusb\hotplug.h" />
     <ClInclude Include="..\libusb\libusb.h" />
     <ClInclude Include="..\libusb\libusbi.h" />
     <ClInclude Include="..\libusb\os\threads_windows.h" />
diff --git a/msvc/libusb_static_2019.vcxproj.filters b/msvc/libusb_static_2019.vcxproj.filters
index 2994ca1..a3294da 100644
--- a/msvc/libusb_static_2019.vcxproj.filters
+++ b/msvc/libusb_static_2019.vcxproj.filters
@@ -17,9 +17,6 @@
     <ClInclude Include="..\libusb\os\events_windows.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="..\libusb\hotplug.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
     <ClInclude Include="..\libusb\libusb.h">
       <Filter>Header Files</Filter>
     </ClInclude>
diff --git a/test b/test
deleted file mode 100644
index e69de29..0000000
--- a/test
+++ /dev/null
diff --git a/tests/Makefile.am b/tests/Makefile.am
index cb8fad9..cf6237e 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -2,6 +2,17 @@
 LDADD = ../libusb/libusb-1.0.la
 LIBS =
 
+stress_SOURCES = stress.c libusb_testlib.h testlib.c
+
 noinst_PROGRAMS = stress
 
-stress_SOURCES = stress.c libusb_testlib.h testlib.c
+if BUILD_UMOCKDEV_TEST
+# NOTE: We add libumockdev-preload.so so that we can run tests in-process
+#       We also use -Wl,-lxxx as the compiler doesn't need it and libtool
+#       would reorder the flags otherwise.
+umockdev_CPPFLAGS = ${UMOCKDEV_CFLAGS} -I$(top_srcdir)/libusb
+umockdev_LDFLAGS = -Wl,--push-state,--no-as-needed -Wl,-lumockdev-preload -Wl,--pop-state ${UMOCKDEV_LIBS}
+umockdev_SOURCES = umockdev.c
+
+noinst_PROGRAMS += umockdev
+endif
diff --git a/tests/stress.c b/tests/stress.c
index 6dcb8f3..0dc9173 100644
--- a/tests/stress.c
+++ b/tests/stress.c
@@ -130,10 +130,13 @@
 			return TEST_STATUS_FAILURE;
 		}
 
-		/* Enable debug output, to be sure to use the context */
-		libusb_set_option(NULL, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_DEBUG);
+		/* Enable debug output on new context, to be sure to use the context */
 		libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_DEBUG);
 
+		/* Enable debug output on the default context. This should work even before
+		 * the context has been created. */
+		libusb_set_option(NULL, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_DEBUG);
+
 		/* Now create a reference to the default context */
 		r = libusb_init(NULL);
 		if (r != LIBUSB_SUCCESS) {
diff --git a/tests/umockdev.c b/tests/umockdev.c
new file mode 100644
index 0000000..488edc2
--- /dev/null
+++ b/tests/umockdev.c
@@ -0,0 +1,1175 @@
+/*
+ * libusb umockdev based tests
+ *
+ * Copyright (C) 2022 Benjamin Berg <bberg@redhat.com>
+ *
+ * This program 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 program 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 program; If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <linux/ioctl.h>
+#include <linux/usbdevice_fs.h>
+
+#include "libusb.h"
+
+#include "umockdev.h"
+
+#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__)
+#pragma GCC diagnostic ignored "-Wanalyzer-malloc-leak"
+#pragma GCC diagnostic ignored "-Wanalyzer-file-leak"
+#endif
+
+typedef struct {
+	pid_t thread;
+	libusb_context *ctx;
+	enum libusb_log_level level;
+	char *str;
+} LogMessage;
+
+static void
+log_message_free(LogMessage *msg)
+{
+	g_free(msg->str);
+	g_free(msg);
+}
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(LogMessage, log_message_free)
+
+typedef struct _UsbChat UsbChat;
+
+struct _UsbChat {
+	gboolean submit;
+	gboolean reap;
+	UsbChat *reaps;
+	UsbChat *next;
+
+	/* struct usbdevfs_urb */
+	unsigned char type;
+	unsigned char endpoint;
+	int status;
+	unsigned int flags;
+	const unsigned char *buffer;
+	int buffer_length;
+	int actual_length;
+
+	/* <submit urb> */
+	UMockdevIoctlData *submit_urb;
+};
+
+typedef struct {
+	UMockdevTestbed *testbed;
+	UMockdevIoctlBase *handler;
+	struct libusb_context *ctx;
+
+	gchar *root_dir;
+	gchar *sys_dir;
+
+	gboolean libusb_log_silence;
+	GList *libusb_log;
+
+	UsbChat *chat;
+	GList *flying_urbs;
+	GList *discarded_urbs;
+
+	/* GMutex confuses tsan unecessarily */
+	pthread_mutex_t mutex;
+} UMockdevTestbedFixture;
+
+/* Global for log handler */
+static UMockdevTestbedFixture *cur_fixture = NULL;
+
+static void
+log_handler(libusb_context *ctx, enum libusb_log_level level, const char *str)
+{
+	/* May be called from different threads without synchronization! */
+	LogMessage *msg;
+	pid_t tid = gettid();
+
+	g_assert (cur_fixture != NULL);
+	g_assert(pthread_mutex_lock(&cur_fixture->mutex) == 0);
+
+	msg = g_new0(LogMessage, 1);
+	msg->ctx = ctx;
+	msg->level = level;
+	msg->str = g_strchomp (g_strdup(str));
+	msg->thread = tid;
+
+	if (!cur_fixture->libusb_log_silence)
+		g_printerr("%s\n", msg->str);
+
+	cur_fixture->libusb_log = g_list_append(cur_fixture->libusb_log, msg);
+	pthread_mutex_unlock(&cur_fixture->mutex);
+}
+
+static void
+log_handler_null(libusb_context *ctx, enum libusb_log_level level, const char *str)
+{
+	(void) ctx;
+	(void) level;
+	(void) str;
+}
+
+static void
+clear_libusb_log(UMockdevTestbedFixture * fixture, enum libusb_log_level level)
+{
+	g_assert(pthread_mutex_lock(&fixture->mutex) == 0);
+
+	while (fixture->libusb_log) {
+		LogMessage *msg = fixture->libusb_log->data;
+
+		g_assert(msg->ctx == fixture->ctx);
+
+		if (msg->level < level) {
+			pthread_mutex_unlock(&fixture->mutex);
+			return;
+		}
+
+		fixture->libusb_log = g_list_delete_link(fixture->libusb_log, fixture->libusb_log);
+		log_message_free(msg);
+	}
+	pthread_mutex_unlock(&fixture->mutex);
+}
+
+static void
+assert_libusb_log_msg(UMockdevTestbedFixture * fixture, enum libusb_log_level level, const char *re)
+{
+	g_assert(pthread_mutex_lock(&fixture->mutex) == 0);
+
+	while (fixture->libusb_log) {
+		g_autoptr(LogMessage) msg = NULL;
+
+		if (fixture->libusb_log == NULL)
+			g_error ("No level %d message found searching for %s", level, re);
+
+		msg = fixture->libusb_log->data;
+		fixture->libusb_log = g_list_delete_link(fixture->libusb_log, fixture->libusb_log);
+
+		if (msg->ctx != fixture->ctx)
+			g_error ("Saw unexpected message \"%s\" from context %p while %p was expected",
+				 msg->str, msg->ctx, fixture->ctx);
+
+		if (msg->level == level && g_regex_match_simple(re, msg->str, 0, 0)) {
+			pthread_mutex_unlock(&fixture->mutex);
+			return;
+		}
+
+		/* Allow skipping INFO and DEBUG messages */
+		if (msg->level >= LIBUSB_LOG_LEVEL_INFO)
+			continue;
+
+		g_error ("Searched for \"%s\" (%d) but found \"%s\" (%d)", re, level, msg->str, msg->level);
+	}
+
+	pthread_mutex_unlock(&fixture->mutex);
+	g_error ("Searched for \"%s\" (%d) but no message matched", re, level);
+}
+
+static void
+assert_libusb_no_log_msg(UMockdevTestbedFixture * fixture, enum libusb_log_level level, const char *re)
+{
+	g_assert(pthread_mutex_lock(&fixture->mutex) == 0);
+
+	while (fixture->libusb_log) {
+		g_autoptr(LogMessage) msg = NULL;
+		gboolean matching;
+
+		msg = fixture->libusb_log->data;
+		fixture->libusb_log = g_list_delete_link(fixture->libusb_log, fixture->libusb_log);
+
+		g_assert(msg->ctx == fixture->ctx);
+
+		matching = (msg->level == level && g_regex_match_simple(re, msg->str, 0, 0));
+
+		/* Allow skipping INFO and DEBUG messages */
+		if (!matching && msg->level >= LIBUSB_LOG_LEVEL_INFO)
+			continue;
+
+		g_error ("Asserting \"%s\" (%d) not logged and found \"%s\" (%d)", re, level, msg->str, msg->level);
+	}
+
+	pthread_mutex_unlock(&fixture->mutex);
+}
+
+static void
+dump_buffer(const unsigned char *buffer, int len)
+{
+	g_autoptr(GString) line = NULL;
+
+	line = g_string_new ("");
+	for (gint i = 0; i < len; i++) {
+		g_string_append_printf(line, "%02x ", buffer[i]);
+		if ((i + 1) % 16 == 0) {
+			g_printerr("    %s\n", line->str);
+			g_string_set_size(line, 0);
+		}
+	}
+
+	if (line->len)
+		g_printerr("    %s\n", line->str);
+}
+
+static gint
+cmp_ioctl_data_addr(const void *data, const void *addr)
+{
+	return ((const UMockdevIoctlData*) data)->client_addr != (gulong) addr;
+}
+
+static gboolean
+handle_ioctl_cb (UMockdevIoctlBase *handler, UMockdevIoctlClient *client, UMockdevTestbedFixture *fixture)
+{
+	UMockdevIoctlData *ioctl_arg;
+	long int request;
+	struct usbdevfs_urb *urb;
+
+	(void) handler;
+
+	request = umockdev_ioctl_client_get_request (client);
+	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
+	 * the data into a local copy! */
+
+	switch (request) {
+	case USBDEVFS_GET_CAPABILITIES: {
+		g_autoptr(UMockdevIoctlData) d = NULL;
+		d = umockdev_ioctl_data_resolve(ioctl_arg, 0, sizeof(guint32), NULL);
+
+		*(guint32*) d->data = USBDEVFS_CAP_BULK_SCATTER_GATHER |
+				      USBDEVFS_CAP_BULK_CONTINUATION |
+				      USBDEVFS_CAP_NO_PACKET_SIZE_LIM |
+				      USBDEVFS_CAP_REAP_AFTER_DISCONNECT |
+				      USBDEVFS_CAP_ZERO_PACKET;
+
+		umockdev_ioctl_client_complete(client, 0, 0);
+		return TRUE;
+	}
+
+	case USBDEVFS_CLAIMINTERFACE:
+	case USBDEVFS_RELEASEINTERFACE:
+	case USBDEVFS_CLEAR_HALT:
+	case USBDEVFS_RESET:
+	case USBDEVFS_RESETEP:
+		umockdev_ioctl_client_complete(client, 0, 0);
+		return TRUE;
+
+	case USBDEVFS_SUBMITURB: {
+		g_autoptr(UMockdevIoctlData) urb_buffer = NULL;
+		g_autoptr(UMockdevIoctlData) urb_data = NULL;
+		gsize buflen;
+
+		if (!fixture->chat || !fixture->chat->submit)
+			return FALSE;
+
+		buflen = fixture->chat->buffer_length;
+		if (fixture->chat->type == USBDEVFS_URB_TYPE_CONTROL)
+			buflen = 8;
+
+		urb_data = umockdev_ioctl_data_resolve(ioctl_arg, 0, sizeof(struct usbdevfs_urb), NULL);
+		urb = (struct usbdevfs_urb*) urb_data->data;
+		urb_buffer = umockdev_ioctl_data_resolve(urb_data, G_STRUCT_OFFSET(struct usbdevfs_urb, buffer), urb->buffer_length, NULL);
+
+		if (fixture->chat->type == urb->type &&
+		    fixture->chat->endpoint == urb->endpoint &&
+		    fixture->chat->buffer_length == urb->buffer_length &&
+		    (fixture->chat->buffer == NULL || memcmp (fixture->chat->buffer, urb_buffer->data, buflen) == 0)) {
+			fixture->flying_urbs = g_list_append (fixture->flying_urbs, umockdev_ioctl_data_ref(urb_data));
+
+			if (fixture->chat->reaps)
+				fixture->chat->reaps->submit_urb = urb_data;
+
+			if (fixture->chat->status)
+				umockdev_ioctl_client_complete(client, -1, -fixture->chat->status);
+			else
+				umockdev_ioctl_client_complete(client, 0, 0);
+
+			if (fixture->chat->next)
+				fixture->chat = fixture->chat->next;
+			else
+				fixture->chat += 1;
+			return TRUE;
+		}
+
+		/* chat message didn't match, don't accept it */
+		g_printerr("Could not process submit urb:\n");
+		g_printerr(" t: %d, ep: %d, actual_length: %d, buffer_length: %d\n",
+			   urb->type, urb->endpoint, urb->actual_length, urb->buffer_length);
+		if (urb->type == USBDEVFS_URB_TYPE_CONTROL || urb->endpoint & LIBUSB_ENDPOINT_IN)
+			dump_buffer(urb->buffer, urb->buffer_length);
+		g_printerr("Looking for:\n");
+		g_printerr(" t: %d, ep: %d, actual_length: %d, buffer_length: %d\n",
+			   fixture->chat->type, fixture->chat->endpoint,
+			   fixture->chat->actual_length, fixture->chat->buffer_length);
+		if (fixture->chat->buffer)
+			dump_buffer(fixture->chat->buffer, buflen);
+
+		return FALSE;
+	}
+
+	case USBDEVFS_REAPURB:
+	case USBDEVFS_REAPURBNDELAY: {
+		g_autoptr(UMockdevIoctlData) urb_ptr = NULL;
+		g_autoptr(UMockdevIoctlData) urb_data = NULL;
+
+		if (fixture->discarded_urbs) {
+			urb_data = fixture->discarded_urbs->data;
+			urb = (struct usbdevfs_urb*) urb_data->data;
+			fixture->discarded_urbs = g_list_delete_link(fixture->discarded_urbs, fixture->discarded_urbs);
+			urb->status = -ENOENT;
+
+			urb_ptr = umockdev_ioctl_data_resolve(ioctl_arg, 0, sizeof(gpointer), NULL);
+			umockdev_ioctl_data_set_ptr(urb_ptr, 0, urb_data);
+
+			umockdev_ioctl_client_complete(client, 0, 0);
+			return TRUE;
+		}
+
+		if (fixture->chat && fixture->chat->reap) {
+			GList *l = g_list_find(fixture->flying_urbs, fixture->chat->submit_urb);
+
+			if (l) {
+				fixture->flying_urbs = g_list_remove_link(fixture->flying_urbs, fixture->flying_urbs);
+
+				urb_data = fixture->chat->submit_urb;
+				urb = (struct usbdevfs_urb*) urb_data->data;
+				urb->actual_length = fixture->chat->actual_length;
+				if (urb->type == USBDEVFS_URB_TYPE_CONTROL && urb->actual_length)
+					urb->actual_length -= 8;
+				if (fixture->chat->buffer)
+					memcpy(urb->buffer, fixture->chat->buffer, fixture->chat->actual_length);
+				urb->status = fixture->chat->status;
+
+				urb_ptr = umockdev_ioctl_data_resolve(ioctl_arg, 0, sizeof(gpointer), NULL);
+				umockdev_ioctl_data_set_ptr(urb_ptr, 0, urb_data);
+				if (fixture->chat->next)
+					fixture->chat = fixture->chat->next;
+				else
+					fixture->chat += 1;
+				umockdev_ioctl_client_complete(client, 0, 0);
+				return TRUE;
+			}
+		}
+
+		/* Nothing to reap */
+		umockdev_ioctl_client_complete(client, -1, EAGAIN);
+		return TRUE;
+	}
+
+	case USBDEVFS_DISCARDURB: {
+		GList *l = g_list_find_custom(fixture->flying_urbs, *(void**) ioctl_arg->data, cmp_ioctl_data_addr);
+
+		if (l) {
+			fixture->discarded_urbs = g_list_append(fixture->discarded_urbs, l->data);
+			fixture->flying_urbs = g_list_delete_link(fixture->flying_urbs, l);
+			umockdev_ioctl_client_complete(client, 0, 0);
+		} else {
+			umockdev_ioctl_client_complete(client, -1, EINVAL);
+		}
+
+		return TRUE;
+	}
+
+	default:
+		return FALSE;
+	}
+}
+
+static void
+test_fixture_add_canon(UMockdevTestbedFixture * fixture)
+{
+	/* Setup first, so we can be sure libusb_open works when the add uevent
+	 * happens.
+	 */
+	g_assert_cmpint(umockdev_testbed_attach_ioctl(fixture->testbed, "/dev/bus/usb/001/001", fixture->handler, NULL), ==, 1);
+
+	/* NOTE: add_device would not create a file, needed for device emulation */
+	/* XXX: Racy, see https://github.com/martinpitt/umockdev/issues/173 */
+	umockdev_testbed_add_from_string(fixture->testbed,
+		"P: /devices/usb1\n"
+		"N: bus/usb/001/001\n"
+		"E: SUBSYSTEM=usb\n"
+		"E: DRIVER=usb\n"
+		"E: BUSNUM=001\n"
+		"E: DEVNUM=001\n"
+		"E: DEVNAME=/dev/bus/usb/001/001\n"
+		"E: DEVTYPE=usb_device\n"
+		"A: bConfigurationValue=1\\n\n"
+		"A: busnum=1\\n\n"
+		"A: devnum=1\\n\n"
+		"A: bConfigurationValue=1\\n\n"
+		"A: speed=480\\n\n"
+		/* descriptor from a Canon PowerShot SX200; VID 04a9 PID 31c0 */
+		"H: descriptors="
+		  "1201000200000040a904c03102000102"
+		  "030109022700010100c0010904000003"
+		  "06010100070581020002000705020200"
+		  "020007058303080009\n",
+		NULL);
+}
+
+static void
+test_fixture_setup_libusb(UMockdevTestbedFixture * fixture, int devcount)
+{
+	libusb_device **devs = NULL;
+
+	libusb_init (&fixture->ctx);
+
+	/* Supress 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);
+	libusb_set_option (fixture->ctx, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_DEBUG);
+	g_assert_cmpint(libusb_get_device_list(fixture->ctx, &devs), ==, devcount);
+	libusb_free_device_list(devs, TRUE);
+	libusb_set_log_cb (fixture->ctx, log_handler, LIBUSB_LOG_CB_CONTEXT);
+}
+
+static void
+test_fixture_setup_common(UMockdevTestbedFixture * fixture)
+{
+	g_assert(cur_fixture == NULL);
+	cur_fixture = fixture;
+
+	pthread_mutex_init(&fixture->mutex, NULL);
+
+	fixture->testbed = umockdev_testbed_new();
+	g_assert(fixture->testbed != NULL);
+	fixture->root_dir = umockdev_testbed_get_root_dir(fixture->testbed);
+	fixture->sys_dir = umockdev_testbed_get_sys_dir(fixture->testbed);
+
+	fixture->handler = umockdev_ioctl_base_new();
+	g_object_connect(fixture->handler, "signal-after::handle-ioctl", handle_ioctl_cb, fixture, NULL);
+}
+
+static void
+test_fixture_setup_empty(UMockdevTestbedFixture * fixture, UNUSED_DATA)
+{
+	test_fixture_setup_common(fixture);
+
+	test_fixture_setup_libusb(fixture, 0);
+}
+
+static void
+test_fixture_setup_with_canon(UMockdevTestbedFixture * fixture, UNUSED_DATA)
+{
+	test_fixture_setup_common(fixture);
+
+	test_fixture_add_canon(fixture);
+
+	test_fixture_setup_libusb(fixture, 1);
+}
+
+static void
+test_fixture_teardown(UMockdevTestbedFixture * fixture, UNUSED_DATA)
+{
+	g_assert(cur_fixture == fixture);
+
+	/* Abort if there are any warnings/errors in the log */
+	clear_libusb_log(fixture, LIBUSB_LOG_LEVEL_INFO);
+
+	if (fixture->ctx) {
+		libusb_device **devs = NULL;
+		int count = libusb_get_device_list(fixture->ctx, &devs);
+		libusb_free_device_list(devs, TRUE);
+
+		libusb_exit (fixture->ctx);
+
+		/* libusb_exit should result in the correct number of devices being destroyed */
+		for (int i = 0; i < count; i++)
+			assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_DEBUG, "libusb_unref_device");
+
+		assert_libusb_no_log_msg(fixture, LIBUSB_LOG_LEVEL_DEBUG, "libusb_unref_device");
+	}
+	libusb_set_log_cb (NULL, NULL, LIBUSB_LOG_CB_GLOBAL);
+	cur_fixture = NULL;
+
+	/* Abort if there are any warnings/errors in the log */
+	clear_libusb_log(fixture, LIBUSB_LOG_LEVEL_INFO);
+	fixture->ctx = NULL;
+	g_assert_null(fixture->libusb_log);
+
+	g_clear_object(&fixture->handler);
+	g_clear_object(&fixture->testbed);
+
+	/* verify that temp dir gets cleaned up properly */
+	g_assert(!g_file_test(fixture->root_dir, G_FILE_TEST_EXISTS));
+	g_free(fixture->root_dir);
+	g_free(fixture->sys_dir);
+
+	while (fixture->flying_urbs) {
+		umockdev_ioctl_data_unref (fixture->flying_urbs->data);
+		fixture->flying_urbs = g_list_delete_link (fixture->flying_urbs, fixture->flying_urbs);
+	}
+
+	pthread_mutex_destroy(&fixture->mutex);
+}
+
+static void
+test_open_close(UMockdevTestbedFixture * fixture, UNUSED_DATA)
+{
+	libusb_device **devs = NULL;
+	struct libusb_device_descriptor desc;
+	libusb_device_handle *handle = NULL;
+
+	g_assert_cmpint(libusb_get_device_list(fixture->ctx, &devs), ==, 1);
+	/* The linux_enumerate_device may happen from a different thread */
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_DEBUG, "libusb_get_device_list");
+	/* We have exactly one device */
+	g_assert_cmpint(libusb_get_bus_number(devs[0]), ==, 1);
+	g_assert_cmpint(libusb_get_device_address(devs[0]), ==, 1);
+
+	/* Get/Check descriptor */
+	clear_libusb_log(fixture, LIBUSB_LOG_LEVEL_INFO);
+	libusb_get_device_descriptor (devs[0], &desc);
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_DEBUG, "libusb_get_device_descriptor");
+	g_assert_cmpint(desc.idVendor, ==, 0x04a9);
+	g_assert_cmpint(desc.idProduct, ==, 0x31c0);
+
+	/* Open and close */
+	g_assert_cmpint(libusb_open(devs[0], &handle), ==, 0);
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_DEBUG, "usbi_add_event_source");
+	g_assert_nonnull(handle);
+	libusb_close(handle);
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_DEBUG, "usbi_remove_event_source");
+
+	libusb_free_device_list(devs, TRUE);
+
+	/* Open and close using vid/pid */
+	handle = libusb_open_device_with_vid_pid(fixture->ctx, 0x04a9, 0x31c0);
+	g_assert_nonnull(handle);
+	libusb_close(handle);
+}
+
+static void
+test_implicit_default(UMockdevTestbedFixture * fixture, UNUSED_DATA)
+{
+	libusb_device **devs = NULL;
+
+	clear_libusb_log(fixture, LIBUSB_LOG_LEVEL_INFO);
+	g_assert_cmpint(libusb_get_device_list(NULL, &devs), ==, 1);
+	libusb_free_device_list(devs, TRUE);
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_ERROR, "\\[usbi_get_context\\].*implicit default");
+
+	/* Only warns once */
+	g_assert_cmpint(libusb_get_device_list(NULL, &devs), ==, 1);
+	libusb_free_device_list(devs, TRUE);
+	clear_libusb_log(fixture, LIBUSB_LOG_LEVEL_INFO);
+
+	libusb_init(NULL);
+	g_assert_cmpint(libusb_get_device_list(NULL, &devs), ==, 1);
+	libusb_exit(NULL);
+
+	/* We free late, causing a warning from libusb_exit. However,
+	 * we never see this warning (i.e. test success) because it is on a
+	 * different context.
+	 */
+	libusb_free_device_list(devs, TRUE);
+}
+
+static void
+test_close_flying(UMockdevTestbedFixture * fixture, UNUSED_DATA)
+{
+	UsbChat chat[] = {
+		{
+		  .submit = TRUE,
+		  .type = USBDEVFS_URB_TYPE_BULK,
+		  .endpoint = LIBUSB_ENDPOINT_OUT,
+		  .buffer = (unsigned char[]) { 0x01, 0x02, 0x03, 0x04 },
+		  .buffer_length = 4,
+		},
+		{ .submit = FALSE }
+	};
+	libusb_device_handle *handle = NULL;
+	struct libusb_transfer *transfer = NULL;
+
+	fixture->chat = chat;
+
+	/* Open */
+	handle = libusb_open_device_with_vid_pid(fixture->ctx, 0x04a9, 0x31c0);
+	g_assert_nonnull(handle);
+
+	transfer = libusb_alloc_transfer(0);
+	libusb_fill_bulk_transfer(transfer,
+				  handle,
+				  LIBUSB_ENDPOINT_OUT,
+				  (unsigned char*) chat[0].buffer,
+				  chat[0].buffer_length,
+				  NULL,
+				  NULL,
+				  1);
+
+	/* Submit */
+	libusb_submit_transfer(transfer);
+
+	/* Closing logs fat error (two lines) */
+	clear_libusb_log(fixture, LIBUSB_LOG_LEVEL_DEBUG);
+	libusb_close(handle);
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_ERROR, "\\[do_close\\] .*connected as far as we know");
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_ERROR, "\\[do_close\\] .*cancellation hasn't even been scheduled");
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_DEBUG, "\\[do_close\\] Removed transfer");
+
+	/* Free'ing the transfer works, and logs to the right context */
+	libusb_free_transfer(transfer);
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_DEBUG, "\\[libusb_free_transfer\\]");
+}
+
+static void
+test_close_cancelled(UMockdevTestbedFixture * fixture, UNUSED_DATA)
+{
+	UsbChat chat[] = {
+		{
+		  .submit = TRUE,
+		  .type = USBDEVFS_URB_TYPE_BULK,
+		  .endpoint = LIBUSB_ENDPOINT_OUT,
+		  .buffer = (unsigned char[]) { 0x01, 0x02, 0x03, 0x04 },
+		  .buffer_length = 4,
+		},
+		{ .submit = FALSE }
+	};
+	libusb_device_handle *handle = NULL;
+	struct libusb_transfer *transfer = NULL;
+
+	fixture->chat = chat;
+
+	/* Open */
+	handle = libusb_open_device_with_vid_pid(fixture->ctx, 0x04a9, 0x31c0);
+	g_assert_nonnull(handle);
+
+	transfer = libusb_alloc_transfer(0);
+	libusb_fill_bulk_transfer(transfer,
+				  handle,
+				  LIBUSB_ENDPOINT_OUT,
+				  (unsigned char*) chat[0].buffer,
+				  chat[0].buffer_length,
+				  NULL,
+				  NULL,
+				  1);
+
+	/* Submit */
+	libusb_submit_transfer(transfer);
+	libusb_cancel_transfer(transfer);
+
+	/* Closing logs fat error (two lines) */
+	clear_libusb_log(fixture, LIBUSB_LOG_LEVEL_DEBUG);
+	libusb_close(handle);
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_ERROR, "\\[do_close\\] .*connected as far as we know");
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_WARNING, "\\[do_close\\] .*cancellation.*hasn't completed");
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_DEBUG, "\\[do_close\\] Removed transfer");
+
+	libusb_free_transfer(transfer);
+}
+
+static void
+test_ctx_destroy(UMockdevTestbedFixture * fixture, UNUSED_DATA)
+{
+	UsbChat chat[] = {
+		{
+		  .submit = TRUE,
+		  .type = USBDEVFS_URB_TYPE_BULK,
+		  .endpoint = LIBUSB_ENDPOINT_OUT,
+		  .buffer = (unsigned char[]) { 0x01, 0x02, 0x03, 0x04 },
+		  .buffer_length = 4,
+		},
+		{ .submit = FALSE }
+	};
+	libusb_device_handle *handle = NULL;
+	struct libusb_transfer *transfer = NULL;
+
+	fixture->chat = chat;
+
+	/* Open */
+	handle = libusb_open_device_with_vid_pid(fixture->ctx, 0x04a9, 0x31c0);
+	g_assert_nonnull(handle);
+
+	transfer = libusb_alloc_transfer(0);
+	libusb_fill_bulk_transfer(transfer,
+				  handle,
+				  LIBUSB_ENDPOINT_OUT,
+				  (unsigned char*) chat[0].buffer,
+				  chat[0].buffer_length,
+				  NULL,
+				  NULL,
+				  1);
+
+	/* Submit */
+	libusb_submit_transfer(transfer);
+
+	/* Now we are evil and destroy the ctx! */
+	libusb_exit(fixture->ctx);
+
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_WARNING, "\\[libusb_exit\\] device.*still referenced");
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_WARNING, "\\[libusb_exit\\] application left some devices open");
+
+	clear_libusb_log(fixture, LIBUSB_LOG_LEVEL_DEBUG);
+	fixture->ctx = NULL;
+
+	/* XXX: Closing crashes the application as it unref's the NULL pointer */
+	/* libusb_close(handle); */
+
+	libusb_free_transfer(transfer);
+}
+
+static void
+test_get_string_descriptor(UMockdevTestbedFixture * fixture, UNUSED_DATA)
+{
+	unsigned char data[255] = { 0, };
+	libusb_device_handle *handle = NULL;
+	UsbChat chat[] = {
+		{
+		  .submit = TRUE,
+		  .reaps = &chat[1],
+		  .type = USBDEVFS_URB_TYPE_CONTROL,
+		  .buffer_length = 12, /* 8 byte out*/
+		  .buffer = (const unsigned char*) "\x80\x06\x00\x03\x00\x00\x04\x00",
+		}, {
+		  /* String with content 0x0409 (en_US) */
+		  .reap = TRUE,
+		  .actual_length = 12,
+		  .buffer = (const unsigned char*) "\x80\x06\x00\x03\x00\x00\x04\x00\x04\x03\x09\x04",
+		}, {
+		  .submit = TRUE,
+		  .reaps = &chat[3],
+		  .type = USBDEVFS_URB_TYPE_CONTROL,
+		  .buffer_length = 263, /* 8 byte out*/
+		  .buffer = (const unsigned char*) "\x80\x06\x01\x03\x09\x04\xff\x00",
+		}, {
+		  /* 4 byte string, "ab" */
+		  .reap = TRUE,
+		  .actual_length = 14,
+		  .buffer = (const unsigned char*) "\x80\x06\x01\x03\x09\x04\xff\x00\x06\x03\x61\x00\x62\x00",
+		}, {
+		  .submit = TRUE,
+		  .reaps = &chat[5],
+		  .type = USBDEVFS_URB_TYPE_CONTROL,
+		  .buffer_length = 12, /* 8 byte out*/
+		  .buffer = (const unsigned char*) "\x80\x06\x00\x03\x00\x00\x04\x00",
+		}, {
+		  .reap = TRUE,
+		  .status = -ENOENT,
+		}, {
+		  .submit = TRUE,
+		  .status = -ENOENT,
+		  .type = USBDEVFS_URB_TYPE_CONTROL,
+		  .buffer_length = 12, /* 8 byte out*/
+		  .buffer = (const unsigned char*) "\x80\x06\x00\x03\x00\x00\x04\x00",
+		}, {
+		  .submit = FALSE,
+		}
+	};
+
+	fixture->chat = chat;
+
+	handle = libusb_open_device_with_vid_pid(fixture->ctx, 0x04a9, 0x31c0);
+	g_assert_nonnull(handle);
+
+	/* The chat allows us to fetch the descriptor */
+	g_assert_cmpint(libusb_get_string_descriptor_ascii(handle, 1, data, sizeof(data)), ==, 2);
+	g_assert_cmpint(memcmp(data, "ab", 2), ==, 0);
+	clear_libusb_log(fixture, LIBUSB_LOG_LEVEL_DEBUG);
+
+	/* Again, but the URB fails with ENOENT when reaping */
+	g_assert_cmpint(libusb_get_string_descriptor_ascii(handle, 1, data, sizeof(data)), ==, -1);
+	clear_libusb_log(fixture, LIBUSB_LOG_LEVEL_DEBUG);
+
+	/* Again, but the URB fails to submit with ENOENT */
+	g_assert_cmpint(libusb_get_string_descriptor_ascii(handle, 1, data, sizeof(data)), ==, -1);
+	assert_libusb_log_msg(fixture, LIBUSB_LOG_LEVEL_ERROR, "\\[submit_control_transfer\\] submiturb failed, errno=2");
+	clear_libusb_log(fixture, LIBUSB_LOG_LEVEL_DEBUG);
+
+	libusb_close(handle);
+}
+
+static void
+transfer_cb_inc_user_data(struct libusb_transfer *transfer)
+{
+	*(int*)transfer->user_data += 1;
+}
+
+static void
+test_timeout(UMockdevTestbedFixture * fixture, UNUSED_DATA)
+{
+	UsbChat chat[] = {
+		{
+		  .submit = TRUE,
+		  .type = USBDEVFS_URB_TYPE_BULK,
+		  .endpoint = LIBUSB_ENDPOINT_OUT,
+		  .buffer = (unsigned char[]) { 0x01, 0x02, 0x03, 0x04 },
+		  .buffer_length = 4,
+		},
+		{
+		  .submit = FALSE,
+		}
+	};
+	int completed = 0;
+	libusb_device_handle *handle = NULL;
+	struct libusb_transfer *transfer = NULL;
+
+	fixture->chat = chat;
+
+	handle = libusb_open_device_with_vid_pid(fixture->ctx, 0x04a9, 0x31c0);
+	g_assert_nonnull(handle);
+
+	transfer = libusb_alloc_transfer(0);
+	libusb_fill_bulk_transfer(transfer,
+				  handle,
+				  LIBUSB_ENDPOINT_OUT,
+				  (unsigned char*) chat[0].buffer,
+				  chat[0].buffer_length,
+				  transfer_cb_inc_user_data,
+				  &completed,
+				  10);
+
+	libusb_submit_transfer(transfer);
+	while (!completed) {
+		g_assert_cmpint(libusb_handle_events_completed(fixture->ctx, &completed), ==, 0);
+		/* Silence after one iteration. */
+		fixture->libusb_log_silence = TRUE;
+	}
+	fixture->libusb_log_silence = FALSE;
+
+	g_assert_cmpint(transfer->status, ==, LIBUSB_TRANSFER_TIMED_OUT);
+	libusb_free_transfer(transfer);
+
+	libusb_close(handle);
+}
+
+#define THREADED_SUBMIT_URB_SETS 64
+#define THREADED_SUBMIT_URB_IN_FLIGHT 64
+typedef struct {
+	struct libusb_transfer *transfers[THREADED_SUBMIT_URB_IN_FLIGHT * THREADED_SUBMIT_URB_SETS];
+	int submitted;
+	int completed;
+	int done;
+	UMockdevTestbedFixture *fixture;
+} TestThreadedSubmit;
+
+static gpointer
+transfer_submit_all_retry(TestThreadedSubmit *data)
+{
+	for (guint i = 0; i < G_N_ELEMENTS(data->transfers); i++) {
+		while (libusb_submit_transfer(data->transfers[i]) < 0) {
+			assert_libusb_log_msg(data->fixture, LIBUSB_LOG_LEVEL_ERROR, "submit_bulk_transfer");
+			continue;
+		}
+
+		data->submitted += 1;
+	}
+
+	return NULL;
+}
+
+static void
+test_threaded_submit_transfer_cb(struct libusb_transfer *transfer)
+{
+	TestThreadedSubmit *data = transfer->user_data;
+
+	/* We should only be receiving packets in the main thread */
+	g_assert_cmpint (getpid(), ==, gettid());
+
+	/* Check that the transfer buffer has the expected value */
+	g_assert_cmpint (*(int*)transfer->buffer, ==, data->completed);
+	data->completed += 1;
+
+	if (data->completed == G_N_ELEMENTS(data->transfers))
+		data->done = TRUE;
+}
+
+static void
+test_threaded_submit(UMockdevTestbedFixture * fixture, UNUSED_DATA)
+{
+	GThread *thread = NULL;
+	TestThreadedSubmit data = { .fixture = fixture };
+	UsbChat out_msg = {
+		  .submit = TRUE,
+		  .type = USBDEVFS_URB_TYPE_BULK,
+		  .endpoint = LIBUSB_ENDPOINT_IN,
+		  .buffer_length = sizeof(int),
+	};
+	UsbChat in_msg = {
+		  .reap = TRUE,
+		  .actual_length = 4,
+	};
+	UsbChat *c;
+	libusb_device_handle *handle = NULL;
+	int urb;
+
+	handle = libusb_open_device_with_vid_pid(fixture->ctx, 0x04a9, 0x31c0);
+	g_assert_nonnull(handle);
+
+	fixture->libusb_log_silence = TRUE;
+
+	c = fixture->chat = g_new0(UsbChat, G_N_ELEMENTS(data.transfers) * 2 + 1);
+	urb = 0;
+	for (int i = 0; i < THREADED_SUBMIT_URB_SETS; i++) {
+		for (int j = 0; j < THREADED_SUBMIT_URB_IN_FLIGHT; j++) {
+			c[i*2*THREADED_SUBMIT_URB_IN_FLIGHT + j] = out_msg;
+			c[i*2*THREADED_SUBMIT_URB_IN_FLIGHT + j].reaps = &c[(i*2+1)*THREADED_SUBMIT_URB_IN_FLIGHT + j];
+			c[(i*2+1)*THREADED_SUBMIT_URB_IN_FLIGHT + j] = in_msg;
+			c[(i*2+1)*THREADED_SUBMIT_URB_IN_FLIGHT + j].buffer = (unsigned char*) g_new0(int, 1);
+			*(int*) c[(i*2+1)*THREADED_SUBMIT_URB_IN_FLIGHT + j].buffer = urb;
+
+			data.transfers[urb] = libusb_alloc_transfer(0);
+			libusb_fill_bulk_transfer(data.transfers[urb],
+						  handle,
+						  LIBUSB_ENDPOINT_IN,
+						  g_malloc(out_msg.buffer_length),
+						  out_msg.buffer_length,
+						  test_threaded_submit_transfer_cb,
+						  &data,
+						  G_MAXUINT);
+			data.transfers[urb]->flags = LIBUSB_TRANSFER_FREE_BUFFER | LIBUSB_TRANSFER_FREE_TRANSFER;
+			urb++;
+		}
+	}
+
+	thread = g_thread_new("transfer all", (GThreadFunc) transfer_submit_all_retry, &data);
+
+	while (!data.done)
+		g_assert_cmpint(libusb_handle_events_completed(fixture->ctx, &data.done), ==, 0);
+
+	g_thread_join(thread);
+
+	fixture->libusb_log_silence = FALSE;
+	libusb_close(handle);
+
+	for (int i = 0; i < 2 * THREADED_SUBMIT_URB_SETS * THREADED_SUBMIT_URB_SETS; i++)
+		g_clear_pointer ((void**) &c->buffer, g_free);
+	g_free (c);
+}
+
+static int
+hotplug_count_arrival_cb(libusb_context *ctx,
+                         libusb_device  *device,
+                         libusb_hotplug_event event,
+                         void *user_data)
+{
+	g_assert_cmpint(event, ==, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED);
+
+	(void) ctx;
+	(void) device;
+
+	*(int*) user_data += 1;
+
+	return 0;
+}
+
+#ifdef UMOCKDEV_HOTPLUG
+static int
+hotplug_count_removal_cb(libusb_context *ctx,
+                         libusb_device  *device,
+                         libusb_hotplug_event event,
+                         void *user_data)
+{
+	g_assert_cmpint(event, ==, LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT);
+
+	(void) ctx;
+	(void) device;
+
+	*(int*) user_data += 1;
+
+	return 0;
+}
+#endif
+
+static void
+test_hotplug_enumerate(UMockdevTestbedFixture * fixture, UNUSED_DATA)
+{
+	libusb_hotplug_callback_handle handle_enumerate;
+	libusb_hotplug_callback_handle handle_no_enumerate;
+	int event_count_enumerate = 0;
+	int event_count_no_enumerate = 0;
+	struct timeval zero_tv = { 0 };
+	int r;
+
+	r = libusb_hotplug_register_callback(fixture->ctx,
+	                                     LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
+	                                     LIBUSB_HOTPLUG_ENUMERATE,
+	                                     LIBUSB_HOTPLUG_MATCH_ANY,
+	                                     LIBUSB_HOTPLUG_MATCH_ANY,
+	                                     LIBUSB_HOTPLUG_MATCH_ANY,
+	                                     hotplug_count_arrival_cb,
+	                                     &event_count_enumerate,
+	                                     &handle_enumerate);
+	g_assert_cmpint(r, ==, 0);
+
+	r = libusb_hotplug_register_callback(fixture->ctx,
+	                                     LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
+	                                     0,
+	                                     LIBUSB_HOTPLUG_MATCH_ANY,
+	                                     LIBUSB_HOTPLUG_MATCH_ANY,
+	                                     LIBUSB_HOTPLUG_MATCH_ANY,
+	                                     hotplug_count_arrival_cb,
+	                                     &event_count_no_enumerate,
+	                                     &handle_no_enumerate);
+	g_assert_cmpint(r, ==, 0);
+
+	g_assert_cmpint(event_count_enumerate, ==, 1);
+	g_assert_cmpint(event_count_no_enumerate, ==, 0);
+
+	libusb_handle_events_timeout(fixture->ctx, &zero_tv);
+
+	g_assert_cmpint(event_count_enumerate, ==, 1);
+	g_assert_cmpint(event_count_no_enumerate, ==, 0);
+
+	libusb_hotplug_deregister_callback(fixture->ctx, handle_enumerate);
+	libusb_hotplug_deregister_callback(fixture->ctx, handle_no_enumerate);
+
+	libusb_handle_events_timeout(fixture->ctx, &zero_tv);
+
+	g_assert_cmpint(event_count_enumerate, ==, 1);
+	g_assert_cmpint(event_count_no_enumerate, ==, 0);
+}
+
+static void
+test_hotplug_add_remove(UMockdevTestbedFixture * fixture, UNUSED_DATA)
+{
+#ifdef UMOCKDEV_HOTPLUG
+	libusb_device **devs = NULL;
+	libusb_hotplug_callback_handle handle_add;
+	libusb_hotplug_callback_handle handle_remove;
+	int event_count_add = 0;
+	int event_count_remove = 0;
+	struct timeval zero_tv = { 0 };
+	int r;
+
+	r = libusb_hotplug_register_callback(fixture->ctx,
+	                                     LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED,
+	                                     LIBUSB_HOTPLUG_ENUMERATE,
+	                                     LIBUSB_HOTPLUG_MATCH_ANY,
+	                                     LIBUSB_HOTPLUG_MATCH_ANY,
+	                                     LIBUSB_HOTPLUG_MATCH_ANY,
+	                                     hotplug_count_arrival_cb,
+	                                     &event_count_add,
+	                                     &handle_add);
+	g_assert_cmpint(r, ==, 0);
+
+	r = libusb_hotplug_register_callback(fixture->ctx,
+	                                     LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
+	                                     LIBUSB_HOTPLUG_ENUMERATE,
+	                                     LIBUSB_HOTPLUG_MATCH_ANY,
+	                                     LIBUSB_HOTPLUG_MATCH_ANY,
+	                                     LIBUSB_HOTPLUG_MATCH_ANY,
+	                                     hotplug_count_removal_cb,
+	                                     &event_count_remove,
+	                                     &handle_remove);
+	g_assert_cmpint(r, ==, 0);
+
+	/* No device, even going into the mainloop will not call cb. */
+	libusb_handle_events_timeout(fixture->ctx, &zero_tv);
+	g_assert_cmpint(event_count_add, ==, 0);
+	g_assert_cmpint(event_count_remove, ==, 0);
+
+	/* Add a device */
+	test_fixture_add_canon(fixture);
+
+	/* Either the thread has picked it up already, or we do so now. */
+	g_assert_cmpint(libusb_get_device_list(fixture->ctx, &devs), ==, 1);
+	libusb_free_device_list(devs, TRUE);
+
+	/* The hotplug event is pending now, but has not yet fired. */
+	g_assert_cmpint(event_count_add, ==, 0);
+	g_assert_cmpint(event_count_remove, ==, 0);
+
+	/* Fire hotplug event. */
+	libusb_handle_events_timeout(fixture->ctx, &zero_tv);
+	g_assert_cmpint(event_count_add, ==, 1);
+	g_assert_cmpint(event_count_remove, ==, 0);
+
+	umockdev_testbed_uevent(fixture->testbed, "/sys/devices/usb1", "remove");
+	//umockdev_testbed_remove_device(fixture->testbed, "/devices/usb1");
+
+	/* Either the thread has picked it up already, or we do so now. */
+	g_assert_cmpint(libusb_get_device_list(fixture->ctx, &devs), ==, 0);
+	libusb_free_device_list(devs, TRUE);
+
+	/* The hotplug event is pending now, but has not yet fired. */
+	g_assert_cmpint(event_count_add, ==, 1);
+	g_assert_cmpint(event_count_remove, ==, 0);
+
+	/* Fire hotplug event. */
+	libusb_handle_events_timeout(fixture->ctx, &zero_tv);
+	g_assert_cmpint(event_count_add, ==, 1);
+	g_assert_cmpint(event_count_remove, ==, 1);
+
+	libusb_hotplug_deregister_callback(fixture->ctx, handle_add);
+	libusb_hotplug_deregister_callback(fixture->ctx, handle_remove);
+#else
+	(void) fixture;
+	g_test_skip("UMockdev is too old to test hotplug");
+#endif
+}
+
+int
+main(int argc, char **argv)
+{
+	g_test_init(&argc, &argv, NULL);
+
+	g_test_add("/libusb/open-close", UMockdevTestbedFixture, NULL,
+	           test_fixture_setup_with_canon,
+	           test_open_close,
+	           test_fixture_teardown);
+
+	g_test_add("/libusb/implicit-default", UMockdevTestbedFixture, NULL,
+	           test_fixture_setup_with_canon,
+	           test_implicit_default,
+	           test_fixture_teardown);
+
+	g_test_add("/libusb/close-flying", UMockdevTestbedFixture, NULL,
+	           test_fixture_setup_with_canon,
+	           test_close_flying,
+	           test_fixture_teardown);
+	g_test_add("/libusb/close-cancelled", UMockdevTestbedFixture, NULL,
+	           test_fixture_setup_with_canon,
+	           test_close_cancelled,
+	           test_fixture_teardown);
+
+	g_test_add("/libusb/ctx-destroy", UMockdevTestbedFixture, NULL,
+	           test_fixture_setup_with_canon,
+	           test_ctx_destroy,
+	           test_fixture_teardown);
+
+	g_test_add("/libusb/string-descriptor", UMockdevTestbedFixture, NULL,
+	           test_fixture_setup_with_canon,
+	           test_get_string_descriptor,
+	           test_fixture_teardown);
+
+	g_test_add("/libusb/timeout", UMockdevTestbedFixture, NULL,
+	           test_fixture_setup_with_canon,
+	           test_timeout,
+	           test_fixture_teardown);
+
+	g_test_add("/libusb/threaded-submit", UMockdevTestbedFixture, NULL,
+	           test_fixture_setup_with_canon,
+	           test_threaded_submit,
+	           test_fixture_teardown);
+
+	g_test_add("/libusb/hotplug/enumerate", UMockdevTestbedFixture, NULL,
+	           test_fixture_setup_with_canon,
+	           test_hotplug_enumerate,
+	           test_fixture_teardown);
+
+	g_test_add("/libusb/hotplug/add-remove", UMockdevTestbedFixture, NULL,
+	           test_fixture_setup_empty,
+	           test_hotplug_add_remove,
+	           test_fixture_teardown);
+
+	return g_test_run();
+}