core: Introduce platform events abstraction

The way in which system handles or resources are represented differs
greatly between Unix-like operating systems and Windows. Ever since
Windows support was added to libusb, Windows been emulating principles
of Unix-like operating systems such as file descriptors and poll().

This commit introduces an abstraction layer that completely removes the
need to perform any emulation. Fundamentally there are three things that
each platform provides to libusb:

  1) A signallable event
  2) A timer (not required, but useful)
  3) A means to wait for event sources such as the above to be triggered

The POSIX abstraction for Unix-like operating systems uses file
descriptors as the "handles" to the underlying system resources. The
signallable event is implemented using a pipe, the timer as a timerfd
(where supported) and the poll() system call is used to wait for events.

The Windows abstraction uses native HANDLEs as the "handles" to the
underlying system resources. The signallable event is implemented using
a manual-reset event, the timer as a manual-reset waitable timer, and
the WaitForMultipleObjects() system call is used to wait for events.

Closes #252

Signed-off-by: Chris Dickens <christopher.a.dickens@gmail.com>
diff --git a/Xcode/config.h b/Xcode/config.h
index 2366078..3385553 100644
--- a/Xcode/config.h
+++ b/Xcode/config.h
@@ -8,6 +8,9 @@
 /* Define to 1 to enable message logging. */
 #define ENABLE_LOGGING 1
 
+/* Define to 1 if using the POSIX events abstraction. */
+#define EVENTS_POSIX 1
+
 /* On 10.12 and later, use newly available clock_*() functions */
 #if MAC_OS_X_VERSION_MIN_REQUIRED >= 101200
 /* Define to 1 if you have the `clock_gettime' function. */
@@ -26,9 +29,6 @@
 /* Define to 1 if you have the <sys/time.h> header file. */
 #define HAVE_SYS_TIME_H 1
 
-/* Define to 1 if using the POSIX poll() implementation. */
-#define POLL_POSIX 1
-
 /* Define to 1 if using POSIX threads. */
 #define THREADS_POSIX 1
 
diff --git a/Xcode/libusb.xcodeproj/project.pbxproj b/Xcode/libusb.xcodeproj/project.pbxproj
index 54e0ff7..dd33f35 100644
--- a/Xcode/libusb.xcodeproj/project.pbxproj
+++ b/Xcode/libusb.xcodeproj/project.pbxproj
@@ -42,7 +42,6 @@
 		008FBF901628B7E800BC5BE2 /* libusbi.h in Headers */ = {isa = PBXBuildFile; fileRef = 008FBF671628B7E800BC5BE2 /* libusbi.h */; };
 		008FBF921628B7E800BC5BE2 /* darwin_usb.c in Sources */ = {isa = PBXBuildFile; fileRef = 008FBF6C1628B7E800BC5BE2 /* darwin_usb.c */; };
 		008FBF931628B7E800BC5BE2 /* darwin_usb.h in Headers */ = {isa = PBXBuildFile; fileRef = 008FBF6D1628B7E800BC5BE2 /* darwin_usb.h */; };
-		008FBF971628B7E800BC5BE2 /* poll_posix.h in Headers */ = {isa = PBXBuildFile; fileRef = 008FBF711628B7E800BC5BE2 /* poll_posix.h */; };
 		008FBF9A1628B7E800BC5BE2 /* threads_posix.c in Sources */ = {isa = PBXBuildFile; fileRef = 008FBF741628B7E800BC5BE2 /* threads_posix.c */; };
 		008FBF9B1628B7E800BC5BE2 /* threads_posix.h in Headers */ = {isa = PBXBuildFile; fileRef = 008FBF751628B7E800BC5BE2 /* threads_posix.h */; };
 		008FBFA01628B7E800BC5BE2 /* sync.c in Sources */ = {isa = PBXBuildFile; fileRef = 008FBF7A1628B7E800BC5BE2 /* sync.c */; };
@@ -65,8 +64,9 @@
 		008FC0311628BC7800BC5BE2 /* libusb-1.0.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 008FBF311628B79300BC5BE2 /* libusb-1.0.0.dylib */; };
 		1438D77A17A2ED9F00166101 /* hotplug.c in Sources */ = {isa = PBXBuildFile; fileRef = 1438D77817A2ED9F00166101 /* hotplug.c */; };
 		1438D77B17A2ED9F00166101 /* hotplug.h in Headers */ = {isa = PBXBuildFile; fileRef = 1438D77917A2ED9F00166101 /* hotplug.h */; };
-		1438D77D17A2EDCD00166101 /* poll_posix.c in Sources */ = {isa = PBXBuildFile; fileRef = 1438D77C17A2EDCD00166101 /* poll_posix.c */; };
 		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 */; };
 		20468D70243298C100650534 /* sam3u_benchmark.c in Sources */ = {isa = PBXBuildFile; fileRef = 20468D6E243298C100650534 /* sam3u_benchmark.c */; };
 		20468D7E2432990100650534 /* testlibusb.c in Sources */ = {isa = PBXBuildFile; fileRef = 20468D7C2432990000650534 /* testlibusb.c */; };
 		20468D7F2432993300650534 /* libusb-1.0.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 008FBF311628B79300BC5BE2 /* libusb-1.0.0.dylib */; };
@@ -308,7 +308,6 @@
 		008FBF671628B7E800BC5BE2 /* libusbi.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.h; path = libusbi.h; sourceTree = "<group>"; tabWidth = 4; usesTabs = 1; };
 		008FBF6C1628B7E800BC5BE2 /* darwin_usb.c */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.c; path = darwin_usb.c; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
 		008FBF6D1628B7E800BC5BE2 /* darwin_usb.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.h; path = darwin_usb.h; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
-		008FBF711628B7E800BC5BE2 /* poll_posix.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.h; path = poll_posix.h; sourceTree = "<group>"; tabWidth = 4; usesTabs = 1; };
 		008FBF741628B7E800BC5BE2 /* threads_posix.c */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = threads_posix.c; sourceTree = "<group>"; tabWidth = 4; usesTabs = 1; };
 		008FBF751628B7E800BC5BE2 /* threads_posix.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.h; path = threads_posix.h; sourceTree = "<group>"; tabWidth = 4; usesTabs = 1; };
 		008FBF7A1628B7E800BC5BE2 /* sync.c */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = sync.c; sourceTree = "<group>"; tabWidth = 4; usesTabs = 1; };
@@ -332,7 +331,6 @@
 		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; };
-		1438D77C17A2EDCD00166101 /* poll_posix.c */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.c; path = poll_posix.c; 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; };
@@ -340,6 +338,8 @@
 		1443EE8716417E63007E0579 /* libusb.xcconfig */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = text.xcconfig; path = libusb.xcconfig; sourceTree = SOURCE_ROOT; tabWidth = 4; usesTabs = 1; };
 		1443EE8816417E63007E0579 /* release.xcconfig */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = text.xcconfig; path = release.xcconfig; sourceTree = SOURCE_ROOT; tabWidth = 4; usesTabs = 1; };
 		1443EE8916417EA6007E0579 /* libusb_release.xcconfig */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = text.xcconfig; path = libusb_release.xcconfig; sourceTree = SOURCE_ROOT; tabWidth = 4; usesTabs = 1; };
+		2018D95E24E453BA001589B2 /* events_posix.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = events_posix.c; sourceTree = "<group>"; };
+		2018D96024E453D0001589B2 /* events_posix.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = events_posix.h; sourceTree = "<group>"; };
 		20468D67243298AE00650534 /* sam3u_benchmark */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = sam3u_benchmark; sourceTree = BUILT_PRODUCTS_DIR; };
 		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; };
@@ -498,8 +498,8 @@
 			children = (
 				008FBF6C1628B7E800BC5BE2 /* darwin_usb.c */,
 				008FBF6D1628B7E800BC5BE2 /* darwin_usb.h */,
-				1438D77C17A2EDCD00166101 /* poll_posix.c */,
-				008FBF711628B7E800BC5BE2 /* poll_posix.h */,
+				2018D95E24E453BA001589B2 /* events_posix.c */,
+				2018D96024E453D0001589B2 /* events_posix.h */,
 				008FBF741628B7E800BC5BE2 /* threads_posix.c */,
 				008FBF751628B7E800BC5BE2 /* threads_posix.h */,
 			);
@@ -558,10 +558,10 @@
 			files = (
 				008FBFA51628B84200BC5BE2 /* config.h in Headers */,
 				008FBF931628B7E800BC5BE2 /* darwin_usb.h in Headers */,
+				2018D96124E453D0001589B2 /* events_posix.h in Headers */,
 				1438D77B17A2ED9F00166101 /* hotplug.h in Headers */,
 				008FBF891628B7E800BC5BE2 /* libusb.h in Headers */,
 				008FBF901628B7E800BC5BE2 /* libusbi.h in Headers */,
-				008FBF971628B7E800BC5BE2 /* poll_posix.h in Headers */,
 				008FBF9B1628B7E800BC5BE2 /* threads_posix.h in Headers */,
 				008FBFA11628B7E800BC5BE2 /* version.h in Headers */,
 				008FBFA21628B7E800BC5BE2 /* version_nano.h in Headers */,
@@ -826,9 +826,9 @@
 				008FBF861628B7E800BC5BE2 /* core.c in Sources */,
 				008FBF921628B7E800BC5BE2 /* darwin_usb.c in Sources */,
 				008FBF871628B7E800BC5BE2 /* descriptor.c in Sources */,
+				2018D95F24E453BA001589B2 /* events_posix.c in Sources */,
 				1438D77A17A2ED9F00166101 /* hotplug.c in Sources */,
 				008FBF881628B7E800BC5BE2 /* io.c in Sources */,
-				1438D77D17A2EDCD00166101 /* poll_posix.c in Sources */,
 				1438D77F17A2F0EA00166101 /* strerror.c in Sources */,
 				008FBFA01628B7E800BC5BE2 /* sync.c in Sources */,
 				008FBF9A1628B7E800BC5BE2 /* threads_posix.c in Sources */,
diff --git a/android/config.h b/android/config.h
index 341c01f..4a4b995 100644
--- a/android/config.h
+++ b/android/config.h
@@ -26,6 +26,9 @@
 /* Define to 1 to enable message logging. */
 #define ENABLE_LOGGING 1
 
+/* Define to 1 if using the POSIX events abstraction. */
+#define EVENTS_POSIX 1
+
 /* Define to 1 if you have the <asm/types.h> header file. */
 #define HAVE_ASM_TYPES_H 1
 
@@ -41,9 +44,6 @@
 /* Define to 1 if you have the <sys/time.h> header file. */
 #define HAVE_SYS_TIME_H 1
 
-/* Define to 1 if using the POSIX poll() implementation. */
-#define POLL_POSIX 1
-
 /* Define to 1 if using POSIX threads. */
 #define THREADS_POSIX 1
 
diff --git a/android/jni/libusb.mk b/android/jni/libusb.mk
index 3308e79..656f903 100644
--- a/android/jni/libusb.mk
+++ b/android/jni/libusb.mk
@@ -35,7 +35,7 @@
   $(LIBUSB_ROOT_REL)/libusb/sync.c \
   $(LIBUSB_ROOT_REL)/libusb/strerror.c \
   $(LIBUSB_ROOT_REL)/libusb/os/linux_usbfs.c \
-  $(LIBUSB_ROOT_REL)/libusb/os/poll_posix.c \
+  $(LIBUSB_ROOT_REL)/libusb/os/events_posix.c \
   $(LIBUSB_ROOT_REL)/libusb/os/threads_posix.c \
   $(LIBUSB_ROOT_REL)/libusb/os/linux_netlink.c
 
diff --git a/configure.ac b/configure.ac
index d386053..4d3cfae 100644
--- a/configure.ac
+++ b/configure.ac
@@ -75,13 +75,13 @@
 *-darwin*)
 	AC_MSG_RESULT([Darwin/Mac OS X])
 	backend=darwin
-	poll=posix
+	events=posix
 	threads=posix
 	;;
 *-haiku*)
 	AC_MSG_RESULT([Haiku])
 	backend=haiku
-	poll=posix
+	events=posix
 	threads=posix
 	;;
 *-linux* | *-uclinux*)
@@ -96,37 +96,37 @@
 		;;
 	esac
 	backend=linux
-	poll=posix
+	events=posix
 	threads=posix
 	;;
 *-netbsd*)
 	AC_MSG_RESULT([NetBSD])
 	backend=netbsd
-	poll=posix
+	events=posix
 	threads=posix
 	;;
 *-openbsd*)
 	AC_MSG_RESULT([OpenBSD])
 	backend=openbsd
-	poll=posix
+	events=posix
 	threads=posix
 	;;
 *-solaris*)
 	AC_MSG_RESULT([SunOS])
 	backend=sunos
-	poll=posix
+	events=posix
 	threads=posix
 	;;
 *-cygwin*)
 	AC_MSG_RESULT([Windows (using Cygwin)])
 	backend=windows
-	poll=windows
+	events=windows
 	threads=posix
 	;;
 *-mingw* | *msys*)
 	AC_MSG_RESULT([Windows])
 	backend=windows
-	poll=windows
+	events=windows
 	threads=windows
 	test "x$enable_shared" = xyes && create_import_lib=yes
 	EXTRA_CFLAGS="-fno-omit-frame-pointer"
@@ -136,19 +136,19 @@
 	AC_MSG_WARN([The host being compiled for is not supported.])
 	AC_MSG_WARN([The library may compile but will not function in any useful manner.])
 	backend="null"
-	poll=posix
+	events=posix
 	threads="posix"
 	;;
 esac
 
-if test "x$poll" = xposix; then
-	AC_DEFINE([POLL_POSIX], [1], [Define to 1 if using the POSIX poll() implementation.])
+if test "x$events" = xposix; then
+	AC_DEFINE([EVENTS_POSIX], [1], [Define to 1 if using the POSIX events abstraction.])
 	AC_CHECK_TYPES([nfds_t], [], [], [[#include <poll.h>]])
 	AC_CHECK_FUNCS([pipe2])
-elif test "x$poll" = xwindows; then
-	AC_DEFINE([POLL_WINDOWS], [1], [Define to 1 if using the Windows poll() implementation.])
+elif test "x$events" = xwindows; then
+	AC_DEFINE([EVENTS_WINDOWS], [1], [Define to 1 if using the Windows events abstraction.])
 else
-	AC_MSG_ERROR([Unknown poll implementation])
+	AC_MSG_ERROR([Unknown events abstraction])
 fi
 
 if test "x$threads" = xposix; then
@@ -314,6 +314,8 @@
 AM_CONDITIONAL([BUILD_EXAMPLES], [test "x$build_examples" != xno])
 AM_CONDITIONAL([BUILD_TESTS], [test "x$build_tests" != xno])
 AM_CONDITIONAL([CREATE_IMPORT_LIB], [test "x$create_import_lib" = xyes])
+AM_CONDITIONAL([EVENTS_POSIX], [test "x$events" = xposix])
+AM_CONDITIONAL([EVENTS_WINDOWS], [test "x$events" = xwindows])
 AM_CONDITIONAL([HAVE_SIGACTION], [test "x$have_sigaction" = xyes])
 AM_CONDITIONAL([OS_DARWIN], [test "x$backend" = xdarwin])
 AM_CONDITIONAL([OS_HAIKU], [test "x$backend" = xhaiku])
@@ -323,8 +325,6 @@
 AM_CONDITIONAL([OS_OPENBSD], [test "x$backend" = xopenbsd])
 AM_CONDITIONAL([OS_SUNOS], [test "x$backend" = xsunos])
 AM_CONDITIONAL([OS_WINDOWS], [test "x$backend" = xwindows])
-AM_CONDITIONAL([POLL_POSIX], [test "x$poll" = xposix])
-AM_CONDITIONAL([POLL_WINDOWS], [test "x$poll" = xwindows])
 AM_CONDITIONAL([THREADS_POSIX], [test "x$threads" = xposix])
 AM_CONDITIONAL([THREADS_WINDOWS], [test "x$threads" = xwindows])
 AM_CONDITIONAL([USE_UDEV], [test "x$use_udev" = xyes])
diff --git a/libusb/Makefile.am b/libusb/Makefile.am
index 6b93343..72cd502 100644
--- a/libusb/Makefile.am
+++ b/libusb/Makefile.am
@@ -5,13 +5,13 @@
 
 lib_LTLIBRARIES = libusb-1.0.la
 
-POSIX_POLL_SRC = os/poll_posix.h os/poll_posix.c
-WINDOWS_POLL_SRC = os/poll_windows.h os/poll_windows.c
+POSIX_EVENTS_SRC = os/events_posix.h os/events_posix.c
+WINDOWS_EVENTS_SRC = os/events_windows.h os/events_windows.c
 
-if POLL_POSIX
-POLL_SRC = $(POSIX_POLL_SRC)
+if EVENTS_POSIX
+EVENTS_SRC = $(POSIX_EVENTS_SRC)
 else
-POLL_SRC = $(WINDOWS_POLL_SRC)
+EVENTS_SRC = $(WINDOWS_EVENTS_SRC)
 endif
 
 POSIX_THREADS_SRC = os/threads_posix.h os/threads_posix.c
@@ -90,6 +90,6 @@
 libusb_1_0_la_LDFLAGS = $(LT_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 \
-	$(POLL_SRC) $(THREADS_SRC) $(OS_SRC)
+	$(EVENTS_SRC) $(THREADS_SRC) $(OS_SRC)
 
 pkginclude_HEADERS = libusb.h
diff --git a/libusb/core.c b/libusb/core.c
index 89cfec3..692350a 100644
--- a/libusb/core.c
+++ b/libusb/core.c
@@ -1188,44 +1188,6 @@
 	}
 }
 
-/*
- * Signal the event pipe so that the event handling thread will be
- * interrupted to process an internal event.
- */
-int usbi_signal_event(struct libusb_context *ctx)
-{
-	unsigned char dummy = 1;
-	ssize_t r;
-
-	/* write some data on event pipe to interrupt event handlers */
-	r = usbi_write(ctx->event_pipe[1], &dummy, sizeof(dummy));
-	if (r != sizeof(dummy)) {
-		usbi_warn(ctx, "internal signalling write failed");
-		return LIBUSB_ERROR_IO;
-	}
-
-	return 0;
-}
-
-/*
- * Clear the event pipe so that the event handling will no longer be
- * interrupted.
- */
-int usbi_clear_event(struct libusb_context *ctx)
-{
-	unsigned char dummy;
-	ssize_t r;
-
-	/* read some data on event pipe to clear it */
-	r = usbi_read(ctx->event_pipe[0], &dummy, sizeof(dummy));
-	if (r != sizeof(dummy)) {
-		usbi_warn(ctx, "internal signalling read failed");
-		return LIBUSB_ERROR_IO;
-	}
-
-	return 0;
-}
-
 /** \ingroup libusb_dev
  * Wrap a platform-specific system device handle and obtain a libusb device
  * handle for the underlying device. The handle allows you to use libusb to
@@ -1500,7 +1462,7 @@
 		pending_events = usbi_pending_events(ctx);
 		ctx->device_close++;
 		if (!pending_events)
-			usbi_signal_event(ctx);
+			usbi_signal_event(&ctx->event);
 		usbi_mutex_unlock(&ctx->event_data_lock);
 
 		/* take event handling lock */
@@ -1517,7 +1479,7 @@
 		ctx->device_close--;
 		pending_events = usbi_pending_events(ctx);
 		if (!pending_events)
-			usbi_clear_event(ctx);
+			usbi_clear_event(&ctx->event);
 		usbi_mutex_unlock(&ctx->event_data_lock);
 
 		/* Release event handling lock and wake up event waiters */
diff --git a/libusb/hotplug.c b/libusb/hotplug.c
index c02cc56..86aeed8 100644
--- a/libusb/hotplug.c
+++ b/libusb/hotplug.c
@@ -223,7 +223,7 @@
 	pending_events = usbi_pending_events(ctx);
 	list_add_tail(&message->list, &ctx->hotplug_msgs);
 	if (!pending_events)
-		usbi_signal_event(ctx);
+		usbi_signal_event(&ctx->event);
 	usbi_mutex_unlock(&ctx->event_data_lock);
 }
 
@@ -347,7 +347,7 @@
 		pending_events = usbi_pending_events(ctx);
 		ctx->event_flags |= USBI_EVENT_HOTPLUG_CB_DEREGISTERED;
 		if (!pending_events)
-			usbi_signal_event(ctx);
+			usbi_signal_event(&ctx->event);
 		usbi_mutex_unlock(&ctx->event_data_lock);
 	}
 }
diff --git a/libusb/io.c b/libusb/io.c
index 27350fa..31a586e 100644
--- a/libusb/io.c
+++ b/libusb/io.c
@@ -25,10 +25,6 @@
 #include "hotplug.h"
 
 #include <errno.h>
-#ifdef HAVE_TIMERFD
-#include <sys/timerfd.h>
-#include <unistd.h>
-#endif
 
 /**
  * \page libusb_io Synchronous and asynchronous device I/O
@@ -1121,44 +1117,40 @@
 	usbi_mutex_init(&ctx->event_data_lock);
 	usbi_tls_key_create(&ctx->event_handling_key);
 	list_init(&ctx->flying_transfers);
-	list_init(&ctx->ipollfds);
-	list_init(&ctx->removed_ipollfds);
+	list_init(&ctx->event_sources);
+	list_init(&ctx->removed_event_sources);
 	list_init(&ctx->hotplug_msgs);
 	list_init(&ctx->completed_transfers);
 
-	/* FIXME should use an eventfd on kernels that support it */
-	r = usbi_pipe(ctx->event_pipe);
-	if (r < 0) {
-		r = LIBUSB_ERROR_OTHER;
-		goto err;
-	}
-
-	r = usbi_add_pollfd(ctx, ctx->event_pipe[0], POLLIN);
+	r = usbi_create_event(&ctx->event);
 	if (r < 0)
-		goto err_close_pipe;
+		goto err;
 
-#ifdef HAVE_TIMERFD
-	ctx->timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
-	if (ctx->timerfd >= 0) {
-		usbi_dbg("using timerfd for timeouts");
-		r = usbi_add_pollfd(ctx, ctx->timerfd, POLLIN);
+	r = usbi_add_event_source(ctx, USBI_EVENT_OS_HANDLE(&ctx->event), USBI_EVENT_POLL_EVENTS);
+	if (r < 0)
+		goto err_destroy_event;
+
+#ifdef HAVE_OS_TIMER
+	r = usbi_create_timer(&ctx->timer);
+	if (r == 0) {
+		usbi_dbg("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_close_timerfd;
+			goto err_destroy_timer;
 	} else {
-		usbi_dbg("timerfd not available, errno=%d", errno);
+		usbi_dbg("timer not available for timeouts");
 	}
 #endif
 
 	return 0;
 
-#ifdef HAVE_TIMERFD
-err_close_timerfd:
-	close(ctx->timerfd);
-	usbi_remove_pollfd(ctx, ctx->event_pipe[0]);
+#ifdef HAVE_OS_TIMER
+err_destroy_timer:
+	usbi_destroy_timer(&ctx->timer);
+	usbi_remove_event_source(ctx, USBI_EVENT_OS_HANDLE(&ctx->event));
 #endif
-err_close_pipe:
-	usbi_close(ctx->event_pipe[0]);
-	usbi_close(ctx->event_pipe[1]);
+err_destroy_event:
+	usbi_destroy_event(&ctx->event);
 err:
 	usbi_mutex_destroy(&ctx->flying_transfers_lock);
 	usbi_mutex_destroy(&ctx->events_lock);
@@ -1169,35 +1161,34 @@
 	return r;
 }
 
-static void cleanup_removed_pollfds(struct libusb_context *ctx)
+static void cleanup_removed_event_sources(struct libusb_context *ctx)
 {
-	struct usbi_pollfd *ipollfd, *tmp;
+	struct usbi_event_source *ievent_source, *tmp;
 
-	for_each_removed_pollfd_safe(ctx, ipollfd, tmp) {
-		list_del(&ipollfd->list);
-		free(ipollfd);
+	for_each_removed_event_source_safe(ctx, ievent_source, tmp) {
+		list_del(&ievent_source->list);
+		free(ievent_source);
 	}
 }
 
 void usbi_io_exit(struct libusb_context *ctx)
 {
-	usbi_remove_pollfd(ctx, ctx->event_pipe[0]);
-	usbi_close(ctx->event_pipe[0]);
-	usbi_close(ctx->event_pipe[1]);
-#ifdef HAVE_TIMERFD
-	if (usbi_using_timerfd(ctx)) {
-		usbi_remove_pollfd(ctx, ctx->timerfd);
-		close(ctx->timerfd);
+#ifdef HAVE_OS_TIMER
+	if (usbi_using_timer(ctx)) {
+		usbi_remove_event_source(ctx, USBI_TIMER_OS_HANDLE(&ctx->timer));
+		usbi_destroy_timer(&ctx->timer);
 	}
 #endif
+	usbi_remove_event_source(ctx, USBI_EVENT_OS_HANDLE(&ctx->event));
+	usbi_destroy_event(&ctx->event);
 	usbi_mutex_destroy(&ctx->flying_transfers_lock);
 	usbi_mutex_destroy(&ctx->events_lock);
 	usbi_mutex_destroy(&ctx->event_waiters_lock);
 	usbi_cond_destroy(&ctx->event_waiters_cond);
 	usbi_mutex_destroy(&ctx->event_data_lock);
 	usbi_tls_key_delete(ctx->event_handling_key);
-	free(ctx->pollfds);
-	cleanup_removed_pollfds(ctx);
+	cleanup_removed_event_sources(ctx);
+	free(ctx->event_data);
 }
 
 static int calculate_timeout(struct usbi_transfer *itransfer)
@@ -1322,55 +1313,39 @@
 	free(ptr);
 }
 
-#ifdef HAVE_TIMERFD
-static int disarm_timerfd(struct libusb_context *ctx)
-{
-	const struct itimerspec disarm_timer = { { 0, 0 }, { 0, 0 } };
-	int r;
-
-	usbi_dbg(" ");
-	r = timerfd_settime(ctx->timerfd, 0, &disarm_timer, NULL);
-	if (r < 0)
-		return LIBUSB_ERROR_OTHER;
-	else
-		return 0;
-}
-
-/* iterates through the flying transfers, and rearms the timerfd based on the
+/* iterates through the flying transfers, and rearms the timer based on the
  * next upcoming timeout.
  * must be called with flying_list locked.
  * returns 0 on success or a LIBUSB_ERROR code on failure.
  */
-static int arm_timerfd_for_next_timeout(struct libusb_context *ctx)
+#ifdef HAVE_OS_TIMER
+static int arm_timer_for_next_timeout(struct libusb_context *ctx)
 {
 	struct usbi_transfer *itransfer;
 
+	if (!usbi_using_timer(ctx))
+		return 0;
+
 	for_each_transfer(ctx, itransfer) {
 		struct timespec *cur_ts = &itransfer->timeout;
 
 		/* if we've reached transfers of infinite timeout, then we have no
 		 * arming to do */
 		if (!TIMESPEC_IS_SET(cur_ts))
-			goto disarm;
+			break;
 
 		/* act on first transfer that has not already been handled */
 		if (!(itransfer->timeout_flags & (USBI_TRANSFER_TIMEOUT_HANDLED | USBI_TRANSFER_OS_HANDLES_TIMEOUT))) {
-			int r;
-			const struct itimerspec it = { {0, 0},
-				{ cur_ts->tv_sec, cur_ts->tv_nsec } };
 			usbi_dbg("next timeout originally %ums", USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer)->timeout);
-			r = timerfd_settime(ctx->timerfd, TFD_TIMER_ABSTIME, &it, NULL);
-			if (r < 0)
-				return LIBUSB_ERROR_OTHER;
-			return 0;
+			return usbi_arm_timer(&ctx->timer, cur_ts);
 		}
 	}
 
-disarm:
-	return disarm_timerfd(ctx);
+	usbi_dbg("no timeouts, disarming timer");
+	return usbi_disarm_timer(&ctx->timer);
 }
 #else
-static int arm_timerfd_for_next_timeout(struct libusb_context *ctx)
+static inline int arm_timer_for_next_timeout(struct libusb_context *ctx)
 {
 	UNUSED(ctx);
 	return 0;
@@ -1421,19 +1396,13 @@
 	/* otherwise we need to be inserted at the end */
 	list_add_tail(&itransfer->list, &ctx->flying_transfers);
 out:
-#ifdef HAVE_TIMERFD
-	if (first && usbi_using_timerfd(ctx) && TIMESPEC_IS_SET(timeout)) {
+#ifdef HAVE_OS_TIMER
+	if (first && usbi_using_timer(ctx) && TIMESPEC_IS_SET(timeout)) {
 		/* if this transfer has the lowest timeout of all active transfers,
-		 * rearm the timerfd with this transfer's timeout */
-		const struct itimerspec it = { {0, 0},
-			{ timeout->tv_sec, timeout->tv_nsec } };
-		usbi_dbg("arm timerfd for timeout in %ums (first in line)",
+		 * rearm the timer with this transfer's timeout */
+		usbi_dbg("arm timer for timeout in %ums (first in line)",
 			USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer)->timeout);
-		r = timerfd_settime(ctx->timerfd, TFD_TIMER_ABSTIME, &it, NULL);
-		if (r < 0) {
-			usbi_warn(ctx, "failed to arm first timerfd, errno=%d", errno);
-			r = LIBUSB_ERROR_OTHER;
-		}
+		r = usbi_arm_timer(&ctx->timer, timeout);
 	}
 #else
 	UNUSED(first);
@@ -1452,15 +1421,15 @@
 static int remove_from_flying_list(struct usbi_transfer *itransfer)
 {
 	struct libusb_context *ctx = ITRANSFER_CTX(itransfer);
-	int rearm_timerfd;
+	int rearm_timer;
 	int r = 0;
 
 	usbi_mutex_lock(&ctx->flying_transfers_lock);
-	rearm_timerfd = (TIMESPEC_IS_SET(&itransfer->timeout) &&
+	rearm_timer = (TIMESPEC_IS_SET(&itransfer->timeout) &&
 		list_first_entry(&ctx->flying_transfers, struct usbi_transfer, list) == itransfer);
 	list_del(&itransfer->list);
-	if (usbi_using_timerfd(ctx) && rearm_timerfd)
-		r = arm_timerfd_for_next_timeout(ctx);
+	if (rearm_timer)
+		r = arm_timer_for_next_timeout(ctx);
 	usbi_mutex_unlock(&ctx->flying_transfers_lock);
 
 	return r;
@@ -1660,7 +1629,7 @@
 
 	r = remove_from_flying_list(itransfer);
 	if (r < 0)
-		usbi_err(ITRANSFER_CTX(itransfer), "failed to set timer for next timeout, errno=%d", errno);
+		usbi_err(ITRANSFER_CTX(itransfer), "failed to set timer for next timeout");
 
 	usbi_mutex_lock(&itransfer->lock);
 	itransfer->state_flags &= ~USBI_TRANSFER_IN_FLIGHT;
@@ -1731,7 +1700,7 @@
 		pending_events = usbi_pending_events(ctx);
 		list_add_tail(&itransfer->completed_list, &ctx->completed_transfers);
 		if (!pending_events)
-			usbi_signal_event(ctx);
+			usbi_signal_event(&ctx->event);
 		usbi_mutex_unlock(&ctx->event_data_lock);
 	}
 }
@@ -1918,7 +1887,7 @@
 	pending_events = usbi_pending_events(ctx);
 	ctx->event_flags |= USBI_EVENT_USER_INTERRUPT;
 	if (!pending_events)
-		usbi_signal_event(ctx);
+		usbi_signal_event(&ctx->event);
 
 	usbi_mutex_unlock(&ctx->event_data_lock);
 }
@@ -2068,8 +2037,76 @@
 	return r;
 }
 
-#ifdef HAVE_TIMERFD
-static int handle_timerfd_trigger(struct libusb_context *ctx)
+static int handle_event_trigger(struct libusb_context *ctx)
+{
+	struct list_head hotplug_msgs;
+	int r = 0;
+
+	usbi_dbg("event triggered");
+
+	list_init(&hotplug_msgs);
+
+	/* take the the event data lock while processing events */
+	usbi_mutex_lock(&ctx->event_data_lock);
+
+	/* check if someone modified the event sources */
+	if (ctx->event_flags & USBI_EVENT_EVENT_SOURCES_MODIFIED)
+		usbi_dbg("someone updated the event sources");
+
+	if (ctx->event_flags & USBI_EVENT_USER_INTERRUPT) {
+		usbi_dbg("someone purposefully interrupted");
+		ctx->event_flags &= ~USBI_EVENT_USER_INTERRUPT;
+	}
+
+	/* check if someone is closing a device */
+	if (ctx->device_close)
+		usbi_dbg("someone is closing a device");
+
+	/* check for any pending hotplug messages */
+	if (!list_empty(&ctx->hotplug_msgs)) {
+		usbi_dbg("hotplug message received");
+		list_cut(&hotplug_msgs, &ctx->hotplug_msgs);
+	}
+
+	/* complete any pending transfers */
+	while (r == 0 && !list_empty(&ctx->completed_transfers)) {
+		struct usbi_transfer *itransfer =
+			list_first_entry(&ctx->completed_transfers, struct usbi_transfer, completed_list);
+
+		list_del(&itransfer->completed_list);
+		usbi_mutex_unlock(&ctx->event_data_lock);
+		r = usbi_backend.handle_transfer_completion(itransfer);
+		if (r)
+			usbi_err(ctx, "backend handle_transfer_completion failed with error %d", r);
+		usbi_mutex_lock(&ctx->event_data_lock);
+	}
+
+	/* if no further pending events, clear the event */
+	if (!usbi_pending_events(ctx))
+		usbi_clear_event(&ctx->event);
+
+	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);
+	}
+
+	return r;
+}
+
+#ifdef HAVE_OS_TIMER
+static int handle_timer_trigger(struct libusb_context *ctx)
 {
 	int r;
 
@@ -2081,7 +2118,7 @@
 		goto out;
 
 	/* arm for next timeout */
-	r = arm_timerfd_for_next_timeout(ctx);
+	r = arm_timer_for_next_timeout(ctx);
 
 out:
 	usbi_mutex_unlock(&ctx->flying_transfers_lock);
@@ -2093,81 +2130,38 @@
  * doing the same thing. */
 static int handle_events(struct libusb_context *ctx, struct timeval *tv)
 {
-	int r;
-	struct usbi_pollfd *ipollfd;
-	usbi_nfds_t nfds = 0;
-	usbi_nfds_t internal_nfds;
-	struct pollfd *fds = NULL;
-	int timeout_ms;
+	struct usbi_reported_events reported_events;
+	int r, timeout_ms;
 
 	/* prevent attempts to recursively handle events (e.g. calling into
 	 * libusb_handle_events() from within a hotplug or transfer callback) */
-	usbi_mutex_lock(&ctx->event_data_lock);
-	r = 0;
 	if (usbi_handling_events(ctx))
-		r = LIBUSB_ERROR_BUSY;
-	else
-		usbi_start_event_handling(ctx);
-	usbi_mutex_unlock(&ctx->event_data_lock);
+		return LIBUSB_ERROR_BUSY;
 
-	if (r)
-		return r;
-
-	/* there are certain fds that libusb uses internally, currently:
-	 *
-	 *   1) event pipe
-	 *   2) timerfd
-	 *
-	 * the backend will never need to attempt to handle events on these fds, so
-	 * we determine how many fds are in use internally for this context and when
-	 * handle_events() is called in the backend, the pollfd list and count will
-	 * be adjusted to skip over these internal fds */
-	if (usbi_using_timerfd(ctx))
-		internal_nfds = 2;
-	else
-		internal_nfds = 1;
-
-	/* only reallocate the poll fds when the list of poll fds has been modified
-	 * since the last poll, otherwise reuse them to save the additional overhead */
+	/* only reallocate the event source data when the list of event sources has
+	 * been modified since the last handle_events(), otherwise reuse them to
+	 * save the additional overhead */
 	usbi_mutex_lock(&ctx->event_data_lock);
-	/* clean up removed poll fds */
-	cleanup_removed_pollfds(ctx);
-	if (ctx->event_flags & USBI_EVENT_POLLFDS_MODIFIED) {
-		int i = 0;
+	if (ctx->event_flags & USBI_EVENT_EVENT_SOURCES_MODIFIED) {
+		usbi_dbg("event sources modified, reallocating event data");
 
-		usbi_dbg("poll fds modified, reallocating");
+		/* free anything removed since we last ran */
+		cleanup_removed_event_sources(ctx);
 
-		free(ctx->pollfds);
-		ctx->pollfds = NULL;
-
-		/* sanity check - it is invalid for a context to have fewer than the
-		 * required internal fds (memory corruption?) */
-		assert(ctx->pollfds_cnt >= internal_nfds);
-
-		ctx->pollfds = calloc(ctx->pollfds_cnt, sizeof(*ctx->pollfds));
-		if (!ctx->pollfds) {
+		r = usbi_alloc_event_data(ctx);
+		if (r) {
 			usbi_mutex_unlock(&ctx->event_data_lock);
-			r = LIBUSB_ERROR_NO_MEM;
-			goto done;
-		}
-
-		for_each_pollfd(ctx, ipollfd) {
-			struct libusb_pollfd *pollfd = &ipollfd->pollfd;
-			ctx->pollfds[i].fd = pollfd->fd;
-			ctx->pollfds[i].events = pollfd->events;
-			i++;
+			return r;
 		}
 
 		/* reset the flag now that we have the updated list */
-		ctx->event_flags &= ~USBI_EVENT_POLLFDS_MODIFIED;
+		ctx->event_flags &= ~USBI_EVENT_EVENT_SOURCES_MODIFIED;
 
-		/* if no further pending events, clear the event pipe so that we do
-		 * not immediately return from poll */
+		/* if no further pending events, clear the event so that we do
+		 * not immediately return from the wait function */
 		if (!usbi_pending_events(ctx))
-			usbi_clear_event(ctx);
+			usbi_clear_event(&ctx->event);
 	}
-	fds = ctx->pollfds;
-	nfds = ctx->pollfds_cnt;
 	usbi_mutex_unlock(&ctx->event_data_lock);
 
 	timeout_ms = (int)(tv->tv_sec * 1000) + (tv->tv_usec / 1000);
@@ -2176,141 +2170,38 @@
 	if (tv->tv_usec % 1000)
 		timeout_ms++;
 
-	usbi_dbg("poll() %d fds with timeout in %dms", (int)nfds, timeout_ms);
-	r = usbi_poll(fds, nfds, timeout_ms);
-	usbi_dbg("poll() returned %d", r);
-	if (r == 0) {
-		r = handle_timeouts(ctx);
-		goto done;
-	} else if (r == -1 && errno == EINTR) {
-		r = LIBUSB_ERROR_INTERRUPTED;
-		goto done;
-	} else if (r < 0) {
-		usbi_err(ctx, "poll failed, errno=%d", errno);
-		r = LIBUSB_ERROR_IO;
+	usbi_start_event_handling(ctx);
+
+	r = usbi_wait_for_events(ctx, &reported_events, timeout_ms);
+	if (r != LIBUSB_SUCCESS) {
+		if (r == LIBUSB_ERROR_TIMEOUT)
+			r = handle_timeouts(ctx);
 		goto done;
 	}
 
-	/* fds[0] is always the event pipe */
-	if (fds[0].revents) {
-		struct list_head hotplug_msgs;
-		struct usbi_transfer *itransfer;
-		int hotplug_cb_deregistered = 0;
-		int ret = 0;
-
-		list_init(&hotplug_msgs);
-
-		usbi_dbg("caught a fish on the event pipe");
-
-		/* take the the event data lock while processing events */
-		usbi_mutex_lock(&ctx->event_data_lock);
-
-		/* check if someone added a new poll fd */
-		if (ctx->event_flags & USBI_EVENT_POLLFDS_MODIFIED)
-			usbi_dbg("someone updated the poll fds");
-
-		if (ctx->event_flags & USBI_EVENT_USER_INTERRUPT) {
-			usbi_dbg("someone purposely interrupted");
-			ctx->event_flags &= ~USBI_EVENT_USER_INTERRUPT;
-		}
-
-		if (ctx->event_flags & USBI_EVENT_HOTPLUG_CB_DEREGISTERED) {
-			usbi_dbg("someone unregistered a hotplug cb");
-			ctx->event_flags &= ~USBI_EVENT_HOTPLUG_CB_DEREGISTERED;
-			hotplug_cb_deregistered = 1;
-		}
-
-		/* check if someone is closing a device */
-		if (ctx->device_close)
-			usbi_dbg("someone is closing a device");
-
-		/* check for any pending hotplug messages */
-		if (!list_empty(&ctx->hotplug_msgs)) {
-			usbi_dbg("hotplug message received");
-			list_cut(&hotplug_msgs, &ctx->hotplug_msgs);
-		}
-
-		/* complete any pending transfers */
-		while (ret == 0 && !list_empty(&ctx->completed_transfers)) {
-			itransfer = list_first_entry(&ctx->completed_transfers, struct usbi_transfer, completed_list);
-			list_del(&itransfer->completed_list);
-			usbi_mutex_unlock(&ctx->event_data_lock);
-			ret = usbi_backend.handle_transfer_completion(itransfer);
-			if (ret)
-				usbi_err(ctx, "backend handle_transfer_completion failed with error %d", ret);
-			usbi_mutex_lock(&ctx->event_data_lock);
-		}
-
-		/* if no further pending events, clear the event pipe */
-		if (!usbi_pending_events(ctx))
-			usbi_clear_event(ctx);
-
-		usbi_mutex_unlock(&ctx->event_data_lock);
-
-		if (hotplug_cb_deregistered)
-			usbi_hotplug_deregister(ctx, 0);
-
-		/* 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 (LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT == message->event)
-				libusb_unref_device(message->device);
-
-			list_del(&message->list);
-			free(message);
-		}
-
-		if (ret) {
+	if (reported_events.event_triggered) {
+		r = handle_event_trigger(ctx);
+		if (r) {
 			/* return error code */
-			r = ret;
 			goto done;
 		}
-
-		if (0 == --r)
-			goto done;
 	}
 
-#ifdef HAVE_TIMERFD
-	/* on timerfd configurations, fds[1] is the timerfd */
-	if (usbi_using_timerfd(ctx) && fds[1].revents) {
-		/* timerfd indicates that a timeout has expired */
-		int ret;
-		usbi_dbg("timerfd triggered");
-
-		ret = handle_timerfd_trigger(ctx);
-		if (ret < 0) {
+#ifdef HAVE_OS_TIMER
+	if (reported_events.timer_triggered) {
+		r = handle_timer_trigger(ctx);
+		if (r) {
 			/* return error code */
-			r = ret;
 			goto done;
 		}
-
-		if (0 == --r)
-			goto done;
 	}
 #endif
 
-	for_each_removed_pollfd(ctx, ipollfd) {
-		usbi_nfds_t n;
+	if (!reported_events.num_ready)
+		goto done;
 
-		for (n = internal_nfds ; n < nfds ; n++) {
-			if (ipollfd->pollfd.fd == fds[n].fd) {
-				/* pollfd was removed between the creation of the fd
-				 * array and here. remove any triggered revent as
-				 * it is no longer relevant */
-				usbi_dbg("pollfd %d was removed. ignoring raised events",
-					 fds[n].fd);
-				fds[n].revents = 0;
-				break;
-			}
-		}
-	}
-
-	r = usbi_backend.handle_events(ctx, fds + internal_nfds, nfds - internal_nfds, r);
+	r = usbi_backend.handle_events(ctx, reported_events.event_data,
+		reported_events.event_data_count, reported_events.num_ready);
 	if (r)
 		usbi_err(ctx, "backend handle_events failed with error %d", r);
 
@@ -2555,13 +2446,8 @@
  */
 int API_EXPORTED libusb_pollfds_handle_timeouts(libusb_context *ctx)
 {
-#ifdef HAVE_TIMERFD
 	ctx = usbi_get_context(ctx);
-	return usbi_using_timerfd(ctx);
-#else
-	UNUSED(ctx);
-	return 0;
-#endif
+	return usbi_using_timer(ctx);
 }
 
 /** \ingroup libusb_poll
@@ -2601,7 +2487,7 @@
 	int r;
 
 	ctx = usbi_get_context(ctx);
-	if (usbi_using_timerfd(ctx))
+	if (usbi_using_timer(ctx))
 		return 0;
 
 	usbi_mutex_lock(&ctx->flying_transfers_lock);
@@ -2673,80 +2559,92 @@
 	libusb_pollfd_added_cb added_cb, libusb_pollfd_removed_cb removed_cb,
 	void *user_data)
 {
+#if !defined(_WIN32) && !defined(__CYGWIN__)
 	ctx = usbi_get_context(ctx);
 	ctx->fd_added_cb = added_cb;
 	ctx->fd_removed_cb = removed_cb;
 	ctx->fd_cb_user_data = user_data;
+#else
+	usbi_err(ctx, "external polling of libusb's internal event sources " \
+		"is not yet supported on Windows");
+	UNUSED(added_cb);
+	UNUSED(removed_cb);
+	UNUSED(user_data);
+#endif
 }
 
 /*
  * Interrupt the iteration of the event handling thread, so that it picks
- * up the fd change. Callers of this function must hold the event_data_lock.
+ * up the event source change. Callers of this function must hold the event_data_lock.
  */
-static void usbi_fd_notification(struct libusb_context *ctx)
+static void usbi_event_source_notification(struct libusb_context *ctx)
 {
 	int pending_events;
 
 	/* Record that there is a new poll fd.
 	 * Only signal an event if there are no prior pending events. */
 	pending_events = usbi_pending_events(ctx);
-	ctx->event_flags |= USBI_EVENT_POLLFDS_MODIFIED;
+	ctx->event_flags |= USBI_EVENT_EVENT_SOURCES_MODIFIED;
 	if (!pending_events)
-		usbi_signal_event(ctx);
+		usbi_signal_event(&ctx->event);
 }
 
-/* Add a file descriptor to the list of file descriptors to be monitored.
- * events should be specified as a bitmask of events passed to poll(), e.g.
+/* Add an event source to the list of event sources to be monitored.
+ * poll_events should be specified as a bitmask of events passed to poll(), e.g.
  * POLLIN and/or POLLOUT. */
-int usbi_add_pollfd(struct libusb_context *ctx, int fd, short events)
+int usbi_add_event_source(struct libusb_context *ctx, usbi_os_handle_t os_handle, short poll_events)
 {
-	struct usbi_pollfd *ipollfd = malloc(sizeof(*ipollfd));
-	if (!ipollfd)
+	struct usbi_event_source *ievent_source = malloc(sizeof(*ievent_source));
+
+	if (!ievent_source)
 		return LIBUSB_ERROR_NO_MEM;
 
-	usbi_dbg("add fd %d events %d", fd, events);
-	ipollfd->pollfd.fd = fd;
-	ipollfd->pollfd.events = events;
+	usbi_dbg("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);
-	list_add_tail(&ipollfd->list, &ctx->ipollfds);
-	ctx->pollfds_cnt++;
-	usbi_fd_notification(ctx);
+	list_add_tail(&ievent_source->list, &ctx->event_sources);
+	usbi_event_source_notification(ctx);
 	usbi_mutex_unlock(&ctx->event_data_lock);
 
+#if !defined(_WIN32) && !defined(__CYGWIN__)
 	if (ctx->fd_added_cb)
-		ctx->fd_added_cb(fd, events, ctx->fd_cb_user_data);
+		ctx->fd_added_cb(os_handle, poll_events, ctx->fd_cb_user_data);
+#endif
+
 	return 0;
 }
 
-/* Remove a file descriptor from the list of file descriptors to be polled. */
-void usbi_remove_pollfd(struct libusb_context *ctx, int fd)
+/* Remove an event source from the list of event sources to be monitored. */
+void usbi_remove_event_source(struct libusb_context *ctx, usbi_os_handle_t os_handle)
 {
-	struct usbi_pollfd *ipollfd;
+	struct usbi_event_source *ievent_source;
 	int found = 0;
 
-	usbi_dbg("remove fd %d", fd);
+	usbi_dbg("remove " USBI_OS_HANDLE_FORMAT_STRING, os_handle);
 	usbi_mutex_lock(&ctx->event_data_lock);
-	for_each_pollfd(ctx, ipollfd) {
-		if (ipollfd->pollfd.fd == fd) {
+	for_each_event_source(ctx, ievent_source) {
+		if (ievent_source->data.os_handle == os_handle) {
 			found = 1;
 			break;
 		}
 	}
 
 	if (!found) {
-		usbi_dbg("couldn't find fd %d to remove", fd);
+		usbi_dbg("couldn't find " USBI_OS_HANDLE_FORMAT_STRING " to remove", os_handle);
 		usbi_mutex_unlock(&ctx->event_data_lock);
 		return;
 	}
 
-	list_del(&ipollfd->list);
-	list_add_tail(&ipollfd->list, &ctx->removed_ipollfds);
-	ctx->pollfds_cnt--;
-	usbi_fd_notification(ctx);
+	list_del(&ievent_source->list);
+	list_add_tail(&ievent_source->list, &ctx->removed_event_sources);
+	usbi_event_source_notification(ctx);
 	usbi_mutex_unlock(&ctx->event_data_lock);
 
+#if !defined(_WIN32) && !defined(__CYGWIN__)
 	if (ctx->fd_removed_cb)
-		ctx->fd_removed_cb(fd, ctx->fd_cb_user_data);
+		ctx->fd_removed_cb(os_handle, ctx->fd_cb_user_data);
+#endif
 }
 
 /** \ingroup libusb_poll
@@ -2768,29 +2666,36 @@
 const struct libusb_pollfd ** LIBUSB_CALL libusb_get_pollfds(
 	libusb_context *ctx)
 {
-#ifndef _WIN32
+#if !defined(_WIN32) && !defined(__CYGWIN__)
 	struct libusb_pollfd **ret = NULL;
-	struct usbi_pollfd *ipollfd;
-	size_t i = 0;
+	struct usbi_event_source *ievent_source;
+	size_t i;
+
+	static_assert(sizeof(struct usbi_event_source_data) == sizeof(struct libusb_pollfd),
+		      "mismatch between usbi_event_source_data and libusb_pollfd sizes");
 
 	ctx = usbi_get_context(ctx);
 
 	usbi_mutex_lock(&ctx->event_data_lock);
 
-	ret = calloc(ctx->pollfds_cnt + 1, sizeof(struct libusb_pollfd *));
+	i = 0;
+	for_each_event_source(ctx, ievent_source)
+		i++;
+
+	ret = calloc(i + 1, sizeof(struct libusb_pollfd *));
 	if (!ret)
 		goto out;
 
-	for_each_pollfd(ctx, ipollfd)
-		ret[i++] = (struct libusb_pollfd *)ipollfd;
-	ret[ctx->pollfds_cnt] = NULL;
+	i = 0;
+	for_each_event_source(ctx, ievent_source)
+		ret[i++] = (struct libusb_pollfd *)ievent_source;
 
 out:
 	usbi_mutex_unlock(&ctx->event_data_lock);
 	return (const struct libusb_pollfd **)ret;
 #else
-	usbi_err(ctx, "external polling of libusb's internal descriptors "\
-		"is not yet supported on Windows platforms");
+	usbi_err(ctx, "external polling of libusb's internal event sources " \
+		"is not yet supported on Windows");
 	return NULL;
 #endif
 }
@@ -2808,7 +2713,11 @@
  */
 void API_EXPORTED libusb_free_pollfds(const struct libusb_pollfd **pollfds)
 {
+#if !defined(_WIN32) && !defined(__CYGWIN__)
 	free((void *)pollfds);
+#else
+	UNUSED(pollfds);
+#endif
 }
 
 /* Backends may call this from handle_events to report disconnection of a
diff --git a/libusb/libusbi.h b/libusb/libusbi.h
index 1677fda..afeb27a 100644
--- a/libusb/libusbi.h
+++ b/libusb/libusbi.h
@@ -74,11 +74,11 @@
 #define PTR_ALIGN(v) \
 	(((v) + (sizeof(void *) - 1)) & ~(sizeof(void *) - 1))
 
-/* Internal abstraction for poll */
-#if defined(POLL_POSIX)
-#include "os/poll_posix.h"
-#elif defined(POLL_WINDOWS)
-#include "os/poll_windows.h"
+/* Internal abstraction for event handling */
+#if defined(EVENTS_POSIX)
+#include "os/events_posix.h"
+#elif defined(EVENTS_WINDOWS)
+#include "os/events_windows.h"
 #endif
 
 /* Internal abstraction for thread synchronization */
@@ -305,8 +305,14 @@
 	libusb_log_cb log_handler;
 #endif
 
-	/* internal event pipe, used for signalling occurrence of an internal event. */
-	int event_pipe[2];
+	/* used for signalling occurrence of an internal event. */
+	usbi_event_t event;
+
+#ifdef HAVE_OS_TIMER
+	/* used for timeout handling, if supported by OS.
+	 * this timer is maintained to trigger on the next pending timeout */
+	usbi_timer_t timer;
+#endif
 
 	struct list_head usb_devs;
 	usbi_mutex_t usb_devs_lock;
@@ -330,10 +336,12 @@
 	 * take this lock first */
 	usbi_mutex_t flying_transfers_lock;
 
+#if !defined(_WIN32) && !defined(__CYGWIN__)
 	/* user callbacks for pollfd changes */
 	libusb_pollfd_added_cb fd_added_cb;
 	libusb_pollfd_removed_cb fd_removed_cb;
 	void *fd_cb_user_data;
+#endif
 
 	/* ensures that only one thread is handling events at any one time */
 	usbi_mutex_t events_lock;
@@ -361,14 +369,17 @@
 	 * in order to safely close a device. Protected by event_data_lock. */
 	unsigned int device_close;
 
-	/* list and count of poll fds and an array of poll fd structures that is
-	 * (re)allocated as necessary prior to polling. Protected by event_data_lock. */
-	struct list_head ipollfds;
-        /* list of pollfds that have been removed. keeps track of pollfd changes
-         * between the poll call and */
-        struct list_head removed_ipollfds;
-	struct pollfd *pollfds;
-	usbi_nfds_t pollfds_cnt;
+	/* A list of currently active event sources. Protected by event_data_lock. */
+	struct list_head event_sources;
+
+	/* A list of event sources that have been removed since the last time
+	 * event sources were waited on. Protected by event_data_lock. */
+	struct list_head removed_event_sources;
+
+	/* A pointer and count to platform-specific data used for monitoring event
+	 * sources. Only accessed during event handling. */
+	void *event_data;
+	unsigned int event_data_cnt;
 
 	/* A list of pending hotplug messages. Protected by event_data_lock. */
 	struct list_head hotplug_msgs;
@@ -376,12 +387,6 @@
 	/* A list of pending completed transfers. Protected by event_data_lock. */
 	struct list_head completed_transfers;
 
-#ifdef HAVE_TIMERFD
-	/* used for timeout handling, if supported by OS.
-	 * this timerfd is maintained to trigger on the next pending timeout */
-	int timerfd;
-#endif
-
 	struct list_head list;
 };
 
@@ -393,8 +398,8 @@
 }
 
 enum usbi_event_flags {
-	/* The list of pollfds has been modified */
-	USBI_EVENT_POLLFDS_MODIFIED = 1U << 0,
+	/* The list of event sources has been modified */
+	USBI_EVENT_EVENT_SOURCES_MODIFIED = 1U << 0,
 
 	/* The user has interrupted the event handler */
 	USBI_EVENT_USER_INTERRUPT = 1U << 1,
@@ -428,16 +433,6 @@
 	       !list_empty(&ctx->completed_transfers);
 }
 
-static inline int usbi_using_timerfd(struct libusb_context *ctx)
-{
-#ifdef HAVE_TIMERFD
-	return ctx->timerfd >= 0;
-#else
-	UNUSED(ctx);
-	return 0;
-#endif
-}
-
 struct libusb_device {
 	/* lock protects refcnt, everything else is finalized at initialization
 	 * time */
@@ -651,18 +646,55 @@
 void usbi_connect_device(struct libusb_device *dev);
 void usbi_disconnect_device(struct libusb_device *dev);
 
-int usbi_signal_event(struct libusb_context *ctx);
-int usbi_clear_event(struct libusb_context *ctx);
-
-struct usbi_pollfd {
-	/* must come first */
-	struct libusb_pollfd pollfd;
-
+struct usbi_event_source {
+	struct usbi_event_source_data {
+		usbi_os_handle_t os_handle;
+		short poll_events;
+	} data;
 	struct list_head list;
 };
 
-int usbi_add_pollfd(struct libusb_context *ctx, int fd, short events);
-void usbi_remove_pollfd(struct libusb_context *ctx, int fd);
+int usbi_add_event_source(struct libusb_context *ctx, usbi_os_handle_t os_handle,
+	short poll_events);
+void usbi_remove_event_source(struct libusb_context *ctx, usbi_os_handle_t os_handle);
+
+/* OS event abstraction */
+
+int usbi_create_event(usbi_event_t *event);
+void usbi_destroy_event(usbi_event_t *event);
+void usbi_signal_event(usbi_event_t *event);
+void usbi_clear_event(usbi_event_t *event);
+
+#ifdef HAVE_OS_TIMER
+int usbi_create_timer(usbi_timer_t *timer);
+void usbi_destroy_timer(usbi_timer_t *timer);
+int usbi_arm_timer(usbi_timer_t *timer, const struct timespec *timeout);
+int usbi_disarm_timer(usbi_timer_t *timer);
+#endif
+
+static inline int usbi_using_timer(struct libusb_context *ctx)
+{
+#ifdef HAVE_OS_TIMER
+	return usbi_timer_valid(&ctx->timer);
+#else
+	UNUSED(ctx);
+	return 0;
+#endif
+}
+
+struct usbi_reported_events {
+	unsigned int event_triggered:1;
+#ifdef HAVE_OS_TIMER
+	unsigned int timer_triggered:1;
+#endif
+	void *event_data;
+	unsigned int event_data_count;
+	unsigned int num_ready;
+};
+
+int usbi_alloc_event_data(struct libusb_context *ctx);
+int usbi_wait_for_events(struct libusb_context *ctx,
+	struct usbi_reported_events *reported_events, int timeout_ms);
 
 /* accessor functions for structure private data */
 
@@ -819,7 +851,7 @@
 	 * and other operations so that those operations can happen (hopefully)
 	 * without hiccup. This is also a good place to inform libusb that it
 	 * should monitor certain file descriptors related to this device -
-	 * see the usbi_add_pollfd() function.
+	 * see the usbi_add_event_source() function.
 	 *
 	 * Your backend should also initialize the device structure
 	 * (dev_handle->dev), which is NULL at the beginning of the call.
@@ -848,7 +880,7 @@
 	 * and other operations so that those operations can happen (hopefully)
 	 * without hiccup. This is also a good place to inform libusb that it
 	 * should monitor certain file descriptors related to this device -
-	 * see the usbi_add_pollfd() function.
+	 * see the usbi_add_event_source() function.
 	 *
 	 * This function should not generate any bus I/O and should not block.
 	 *
@@ -869,9 +901,9 @@
 
 	/* Close a device such that the handle cannot be used again. Your backend
 	 * should destroy any resources that were allocated in the open path.
-	 * This may also be a good place to call usbi_remove_pollfd() to inform
-	 * libusb of any file descriptors associated with this device that should
-	 * no longer be monitored.
+	 * This may also be a good place to call usbi_remove_event_source() to
+	 * inform libusb of any event sources associated with this device that
+	 * should no longer be monitored.
 	 *
 	 * This function is called when the user closes a device handle.
 	 */
@@ -1166,21 +1198,22 @@
 	 */
 	void (*clear_transfer_priv)(struct usbi_transfer *itransfer);
 
-	/* Handle any pending events on file descriptors. Optional.
+	/* Handle any pending events on event sources. Optional.
 	 *
-	 * Provide this function when file descriptors directly indicate device
-	 * or transfer activity. If your backend does not have such file descriptors,
+	 * Provide this function when event sources directly indicate device
+	 * or transfer activity. If your backend does not have such event sources,
 	 * implement the handle_transfer_completion function below.
 	 *
 	 * This involves monitoring any active transfers and processing their
 	 * completion or cancellation.
 	 *
-	 * The function is passed an array of pollfd structures (size nfds)
-	 * as a result of the poll() system call. The num_ready parameter
-	 * indicates the number of file descriptors that have reported events
-	 * (i.e. the poll() return value). This should be enough information
-	 * for you to determine which actions need to be taken on the currently
-	 * active transfers.
+	 * The function is passed a pointer that represents platform-specific
+	 * data for monitoring event sources (size count). This data is to be
+	 * (re)allocated as necessary when event sources are modified.
+	 * The num_ready parameter indicates the number of event sources that
+	 * have reported events. This should be enough information for you to
+	 * determine which actions need to be taken on the currently active
+	 * transfers.
 	 *
 	 * For any cancelled transfers, call usbi_handle_transfer_cancellation().
 	 * For completed transfers, call usbi_handle_transfer_completion().
@@ -1199,13 +1232,13 @@
 	 * Return 0 on success, or a LIBUSB_ERROR code on failure.
 	 */
 	int (*handle_events)(struct libusb_context *ctx,
-		struct pollfd *fds, usbi_nfds_t nfds, int num_ready);
+		void *event_data, unsigned int count, unsigned int num_ready);
 
 	/* Handle transfer completion. Optional.
 	 *
-	 * Provide this function when there are no file descriptors available
-	 * that directly indicate device or transfer activity. If your backend does
-	 * have such file descriptors, implement the handle_events function above.
+	 * Provide this function when there are no event sources available that
+	 * directly indicate device or transfer activity. If your backend does
+	 * have such event sources, implement the handle_events function above.
 	 *
 	 * Your backend must tell the library when a transfer has completed by
 	 * calling usbi_signal_transfer_completion(). You should store any private
@@ -1274,14 +1307,14 @@
 #define for_each_transfer_safe(ctx, t, n) \
 	for_each_safe_helper(t, n, &(ctx)->flying_transfers, struct usbi_transfer)
 
-#define for_each_pollfd(ctx, p) \
-	for_each_helper(p, &(ctx)->ipollfds, struct usbi_pollfd)
+#define for_each_event_source(ctx, e) \
+	for_each_helper(e, &(ctx)->event_sources, struct usbi_event_source)
 
-#define for_each_removed_pollfd(ctx, p) \
-	for_each_helper(p, &(ctx)->removed_ipollfds, struct usbi_pollfd)
+#define for_each_removed_event_source(ctx, e) \
+	for_each_helper(e, &(ctx)->removed_event_sources, struct usbi_event_source)
 
-#define for_each_removed_pollfd_safe(ctx, p, n) \
-	for_each_safe_helper(p, n, &(ctx)->removed_ipollfds, struct usbi_pollfd)
+#define for_each_removed_event_source_safe(ctx, e, n) \
+	for_each_safe_helper(e, n, &(ctx)->removed_event_sources, struct usbi_event_source)
 
 #ifdef __cplusplus
 }
diff --git a/libusb/os/events_posix.c b/libusb/os/events_posix.c
new file mode 100644
index 0000000..01064af
--- /dev/null
+++ b/libusb/os/events_posix.c
@@ -0,0 +1,275 @@
+/*
+ * libusb event abstraction on POSIX platforms
+ *
+ * Copyright © 2020 Chris Dickens <christopher.a.dickens@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "libusbi.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#ifdef HAVE_TIMERFD
+#include <sys/timerfd.h>
+#endif
+#include <unistd.h>
+
+#ifdef HAVE_NFDS_T
+typedef nfds_t usbi_nfds_t;
+#else
+typedef unsigned int usbi_nfds_t;
+#endif
+
+int usbi_create_event(usbi_event_t *event)
+{
+#if defined(HAVE_PIPE2)
+	int ret = pipe2(event->pipefd, O_CLOEXEC);
+#else
+	int ret = pipe(event->pipefd);
+#endif
+
+	if (ret != 0) {
+		usbi_err(NULL, "failed to create pipe, errno=%d", errno);
+		return LIBUSB_ERROR_OTHER;
+	}
+
+#if !defined(HAVE_PIPE2) && defined(FD_CLOEXEC)
+	ret = fcntl(event->pipefd[0], F_GETFD);
+	if (ret == -1) {
+		usbi_err(NULL, "failed to get pipe fd flags, errno=%d", errno);
+		goto err_close_pipe;
+	}
+	ret = fcntl(event->pipefd[0], F_SETFD, ret | FD_CLOEXEC);
+	if (ret == -1) {
+		usbi_err(NULL, "failed to set pipe fd flags, errno=%d", errno);
+		goto err_close_pipe;
+	}
+
+	ret = fcntl(event->pipefd[1], F_GETFD);
+	if (ret == -1) {
+		usbi_err(NULL, "failed to get pipe fd flags, errno=%d", errno);
+		goto err_close_pipe;
+	}
+	ret = fcntl(event->pipefd[1], F_SETFD, ret | FD_CLOEXEC);
+	if (ret == -1) {
+		usbi_err(NULL, "failed to set pipe fd flags, errno=%d", errno);
+		goto err_close_pipe;
+	}
+#endif
+
+	ret = fcntl(event->pipefd[1], F_GETFL);
+	if (ret == -1) {
+		usbi_err(NULL, "failed to get pipe fd status flags, errno=%d", errno);
+		goto err_close_pipe;
+	}
+	ret = fcntl(event->pipefd[1], F_SETFL, ret | O_NONBLOCK);
+	if (ret == -1) {
+		usbi_err(NULL, "failed to set pipe fd status flags, errno=%d", errno);
+		goto err_close_pipe;
+	}
+
+	return 0;
+
+err_close_pipe:
+	close(event->pipefd[1]);
+	close(event->pipefd[0]);
+	return LIBUSB_ERROR_OTHER;
+}
+
+void usbi_destroy_event(usbi_event_t *event)
+{
+	if (close(event->pipefd[1]) == -1)
+		usbi_warn(NULL, "failed to close pipe write end, errno=%d", errno);
+	if (close(event->pipefd[0]) == -1)
+		usbi_warn(NULL, "failed to close pipe read end, errno=%d", errno);
+}
+
+void usbi_signal_event(usbi_event_t *event)
+{
+	unsigned char dummy = 1;
+	ssize_t r;
+
+	r = write(event->pipefd[1], &dummy, sizeof(dummy));
+	if (r != sizeof(dummy))
+		usbi_warn(NULL, "pipe write failed");
+}
+
+void usbi_clear_event(usbi_event_t *event)
+{
+	unsigned char dummy;
+	ssize_t r;
+
+	r = read(event->pipefd[0], &dummy, sizeof(dummy));
+	if (r != sizeof(dummy))
+		usbi_warn(NULL, "pipe read failed");
+}
+
+#ifdef HAVE_TIMERFD
+int usbi_create_timer(usbi_timer_t *timer)
+{
+	timer->timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
+	if (timer->timerfd == -1) {
+		usbi_warn(NULL, "failed to create timerfd, errno=%d", errno);
+		return LIBUSB_ERROR_OTHER;
+	}
+
+	return 0;
+}
+
+void usbi_destroy_timer(usbi_timer_t *timer)
+{
+	if (close(timer->timerfd) == -1)
+		usbi_warn(NULL, "failed to close timerfd, errno=%d", errno);
+}
+
+int usbi_arm_timer(usbi_timer_t *timer, const struct timespec *timeout)
+{
+	const struct itimerspec it = { { 0, 0 }, { timeout->tv_sec, timeout->tv_nsec } };
+
+	if (timerfd_settime(timer->timerfd, TFD_TIMER_ABSTIME, &it, NULL) == -1) {
+		usbi_warn(NULL, "failed to arm timerfd, errno=%d", errno);
+		return LIBUSB_ERROR_OTHER;
+	}
+
+	return 0;
+}
+
+int usbi_disarm_timer(usbi_timer_t *timer)
+{
+	const struct itimerspec it = { { 0, 0 }, { 0, 0 } };
+
+	if (timerfd_settime(timer->timerfd, 0, &it, NULL) == -1) {
+		usbi_warn(NULL, "failed to disarm timerfd, errno=%d", errno);
+		return LIBUSB_ERROR_OTHER;
+	}
+
+	return 0;
+}
+#endif
+
+int usbi_alloc_event_data(struct libusb_context *ctx)
+{
+	struct usbi_event_source *ievent_source;
+	struct pollfd *fds;
+	size_t i = 0;
+
+	if (ctx->event_data) {
+		free(ctx->event_data);
+		ctx->event_data = NULL;
+	}
+
+	ctx->event_data_cnt = 0;
+	for_each_event_source(ctx, ievent_source)
+		ctx->event_data_cnt++;
+
+	fds = calloc(ctx->event_data_cnt, sizeof(*fds));
+	if (!fds)
+		return LIBUSB_ERROR_NO_MEM;
+
+	for_each_event_source(ctx, ievent_source) {
+		fds[i].fd = ievent_source->data.os_handle;
+		fds[i].events = ievent_source->data.poll_events;
+		i++;
+	}
+
+	ctx->event_data = fds;
+	return 0;
+}
+
+int usbi_wait_for_events(struct libusb_context *ctx,
+	struct usbi_reported_events *reported_events, int timeout_ms)
+{
+	struct pollfd *fds = ctx->event_data;
+	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);
+	num_ready = poll(fds, nfds, timeout_ms);
+	usbi_dbg("poll() returned %d", num_ready);
+	if (num_ready == 0) {
+		if (usbi_using_timer(ctx))
+			goto done;
+		return LIBUSB_ERROR_TIMEOUT;
+	} else if (num_ready == -1) {
+		if (errno == EINTR)
+			return LIBUSB_ERROR_INTERRUPTED;
+		usbi_err(ctx, "poll() failed, errno=%d", errno);
+		return LIBUSB_ERROR_IO;
+	}
+
+	/* fds[0] is always the internal signalling event */
+	if (fds[0].revents) {
+		reported_events->event_triggered = 1;
+		num_ready--;
+	} else {
+		reported_events->event_triggered = 0;
+	}
+
+#ifdef HAVE_OS_TIMER
+	/* on timer configurations, fds[1] is the timer */
+	if (usbi_using_timer(ctx) && fds[1].revents) {
+		reported_events->timer_triggered = 1;
+		num_ready--;
+	} else {
+		reported_events->timer_triggered = 0;
+	}
+#endif
+
+	assert(num_ready > 0);
+	if (!num_ready)
+		goto done;
+
+	/* the backend will never need to attempt to handle events on the
+	 * library's internal file descriptors, so we determine how many are
+	 * in use internally for this context and skip these when passing any
+	 * remaining pollfds to the backend. */
+	internal_fds = usbi_using_timer(ctx) ? 2 : 1;
+	fds += internal_fds;
+	nfds -= internal_fds;
+
+	usbi_mutex_lock(&ctx->event_data_lock);
+	if (ctx->event_flags & USBI_EVENT_EVENT_SOURCES_MODIFIED) {
+		struct usbi_event_source *ievent_source;
+
+		for_each_removed_event_source(ctx, ievent_source) {
+			usbi_nfds_t n;
+
+			for (n = 0; n < nfds; n++) {
+				if (ievent_source->data.os_handle != fds[n].fd)
+					continue;
+				if (!fds[n].revents)
+					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);
+				fds[n].revents = 0;
+				num_ready--;
+				break;
+			}
+		}
+	}
+	usbi_mutex_unlock(&ctx->event_data_lock);
+
+	if (num_ready) {
+		assert(num_ready > 0);
+		reported_events->event_data = fds;
+		reported_events->event_data_count = (unsigned int)nfds;
+	}
+
+done:
+	reported_events->num_ready = num_ready;
+	return LIBUSB_SUCCESS;
+}
diff --git a/libusb/os/events_posix.h b/libusb/os/events_posix.h
new file mode 100644
index 0000000..401de04
--- /dev/null
+++ b/libusb/os/events_posix.h
@@ -0,0 +1,50 @@
+/*
+ * libusb event abstraction on POSIX platforms
+ *
+ * Copyright © 2020 Chris Dickens <christopher.a.dickens@gmail.com>
+ *
+ * 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 LIBUSB_EVENTS_POSIX_H
+#define LIBUSB_EVENTS_POSIX_H
+
+#include <poll.h>
+
+typedef int usbi_os_handle_t;
+#define USBI_OS_HANDLE_FORMAT_STRING	"fd %d"
+
+typedef struct usbi_event {
+	int pipefd[2];
+} usbi_event_t;
+#define USBI_EVENT_OS_HANDLE(e)	((e)->pipefd[0])
+#define USBI_EVENT_POLL_EVENTS	POLLIN
+#define USBI_INVALID_EVENT	{ { -1, -1 } }
+
+#ifdef HAVE_TIMERFD
+#define HAVE_OS_TIMER 1
+typedef struct usbi_timer {
+	int timerfd;
+} usbi_timer_t;
+#define USBI_TIMER_OS_HANDLE(t)	((t)->timerfd)
+#define USBI_TIMER_POLL_EVENTS	POLLIN
+
+static inline int usbi_timer_valid(usbi_timer_t *timer)
+{
+	return timer->timerfd >= 0;
+}
+#endif
+
+#endif
diff --git a/libusb/os/events_windows.c b/libusb/os/events_windows.c
new file mode 100644
index 0000000..9f120bb
--- /dev/null
+++ b/libusb/os/events_windows.c
@@ -0,0 +1,219 @@
+/*
+ * libusb event abstraction on Microsoft Windows
+ *
+ * Copyright © 2020 Chris Dickens <christopher.a.dickens@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+#include "libusbi.h"
+#include "windows_common.h"
+
+int usbi_create_event(usbi_event_t *event)
+{
+	event->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+	if (event->hEvent == NULL) {
+		usbi_err(NULL, "CreateEvent failed: %s", windows_error_str(0));
+		return LIBUSB_ERROR_OTHER;
+	}
+
+	return 0;
+}
+
+void usbi_destroy_event(usbi_event_t *event)
+{
+	if (!CloseHandle(event->hEvent))
+		usbi_warn(NULL, "CloseHandle failed: %s", windows_error_str(0));
+}
+
+void usbi_signal_event(usbi_event_t *event)
+{
+	if (!SetEvent(event->hEvent))
+		usbi_warn(NULL, "SetEvent failed: %s", windows_error_str(0));
+}
+
+void usbi_clear_event(usbi_event_t *event)
+{
+	if (!ResetEvent(event->hEvent))
+		usbi_warn(NULL, "ResetEvent failed: %s", windows_error_str(0));
+}
+
+#ifdef HAVE_OS_TIMER
+int usbi_create_timer(usbi_timer_t *timer)
+{
+	timer->hTimer = CreateWaitableTimer(NULL, TRUE, NULL);
+	if (timer->hTimer == NULL) {
+		usbi_warn(NULL, "CreateWaitableTimer failed: %s", windows_error_str(0));
+		return LIBUSB_ERROR_OTHER;
+	}
+
+	return 0;
+}
+
+void usbi_destroy_timer(usbi_timer_t *timer)
+{
+	if (!CloseHandle(timer->hTimer))
+		usbi_warn(NULL, "CloseHandle failed: %s", windows_error_str(0));
+}
+
+int usbi_arm_timer(usbi_timer_t *timer, const struct timespec *timeout)
+{
+	struct timespec systime, remaining;
+	FILETIME filetime;
+	LARGE_INTEGER dueTime;
+	int r;
+
+	/* Transfer timeouts are based on the monotonic clock and the waitable
+	 * timers on the system clock. This requires a conversion between the
+	 * two, so we calculate the remaining time relative to the monotonic
+	 * clock and calculate an absolute system time for the timer expiration.
+	 * Note that if the timeout has already passed, the remaining time will
+	 * be negative and thus an absolute system time in the past will be set.
+	 * This works just as intended because the timer becomes signalled
+	 * immediately. */
+	r = usbi_clock_gettime(USBI_CLOCK_MONOTONIC, &systime);
+	if (r < 0) {
+		usbi_err(NULL, "failed to read monotonic clock");
+		return LIBUSB_ERROR_OTHER;
+	}
+
+	TIMESPEC_SUB(timeout, &systime, &remaining);
+
+	GetSystemTimeAsFileTime(&filetime);
+	dueTime.LowPart = filetime.dwLowDateTime;
+	dueTime.HighPart = filetime.dwHighDateTime;
+	dueTime.QuadPart += (remaining.tv_sec * 10000000LL) + (remaining.tv_nsec / 100LL);
+
+	if (!SetWaitableTimer(timer->hTimer, &dueTime, 0, NULL, NULL, FALSE)) {
+		usbi_warn(NULL, "SetWaitableTimer failed: %s", windows_error_str(0));
+		return LIBUSB_ERROR_OTHER;
+	}
+
+	return 0;
+}
+
+int usbi_disarm_timer(usbi_timer_t *timer)
+{
+	LARGE_INTEGER dueTime;
+
+	/* A manual-reset waitable timer will stay in the signalled state until
+	 * another call to SetWaitableTimer() is made. It is possible that the
+	 * timer has already expired by the time we come in to disarm it, so to
+	 * be entirely sure the timer is disarmed and not in the signalled state,
+	 * we will set it with an impossibly large expiration and immediately
+	 * cancel. */
+	dueTime.QuadPart = LLONG_MAX;
+	if (!SetWaitableTimer(timer->hTimer, &dueTime, 0, NULL, NULL, FALSE)) {
+		usbi_warn(NULL, "SetWaitableTimer failed: %s", windows_error_str(0));
+		return LIBUSB_ERROR_OTHER;
+	}
+
+	if (!CancelWaitableTimer(timer->hTimer)) {
+		usbi_warn(NULL, "SetWaitableTimer failed: %s", windows_error_str(0));
+		return LIBUSB_ERROR_OTHER;
+	}
+
+	return 0;
+}
+#endif
+
+int usbi_alloc_event_data(struct libusb_context *ctx)
+{
+	struct usbi_event_source *ievent_source;
+	HANDLE *handles;
+	size_t i = 0;
+
+	/* Event sources are only added during usbi_io_init(). We should not
+	 * be running this function again if the event data has already been
+	 * allocated. */
+	if (ctx->event_data) {
+		usbi_warn(ctx, "program assertion failed - event data already allocated");
+		return LIBUSB_ERROR_OTHER;
+	}
+
+	ctx->event_data_cnt = 0;
+	for_each_event_source(ctx, ievent_source)
+		ctx->event_data_cnt++;
+
+	/* We only expect up to two HANDLEs to wait on, one for the internal
+	 * signalling event and the other for the timer. */
+	if (ctx->event_data_cnt != 1 && ctx->event_data_cnt != 2) {
+		usbi_err(ctx, "program assertion failed - expected exactly 1 or 2 HANDLEs");
+		return LIBUSB_ERROR_OTHER;
+	}
+
+	handles = calloc(ctx->event_data_cnt, sizeof(HANDLE));
+	if (!handles)
+		return LIBUSB_ERROR_NO_MEM;
+
+	for_each_event_source(ctx, ievent_source) {
+		handles[i] = ievent_source->data.os_handle;
+		i++;
+	}
+
+	ctx->event_data = handles;
+	return 0;
+}
+
+int usbi_wait_for_events(struct libusb_context *ctx,
+	struct usbi_reported_events *reported_events, int timeout_ms)
+{
+	HANDLE *handles = ctx->event_data;
+	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);
+	result = WaitForMultipleObjects(num_handles, handles, FALSE, (DWORD)timeout_ms);
+	usbi_dbg("WaitForMultipleObjects() returned %lu", ULONG_CAST(result));
+	if (result == WAIT_TIMEOUT) {
+		if (usbi_using_timer(ctx))
+			goto done;
+		return LIBUSB_ERROR_TIMEOUT;
+	} else if (result == WAIT_FAILED) {
+		usbi_err(ctx, "WaitForMultipleObjects() failed: %s", windows_error_str(0));
+		return LIBUSB_ERROR_IO;
+	}
+
+	result -= WAIT_OBJECT_0;
+
+	/* handles[0] is always the internal signalling event */
+	if (result == 0)
+		reported_events->event_triggered = 1;
+	else
+		reported_events->event_triggered = 0;
+
+#ifdef HAVE_OS_TIMER
+	/* on timer configurations, handles[1] is the timer */
+	if (usbi_using_timer(ctx)) {
+		/* The WaitForMultipleObjects() function reports the index of
+		 * the first object that became signalled. If the internal
+		 * signalling event was reported, we need to also check and
+		 * report whether the timer is in the signalled state. */
+		if (result == 1 || WaitForSingleObject(handles[1], 0) == WAIT_OBJECT_0)
+			reported_events->timer_triggered = 1;
+		else
+			reported_events->timer_triggered = 0;
+	} else {
+		reported_events->timer_triggered = 0;
+	}
+#endif
+
+done:
+	/* no events are ever reported to the backend */
+	reported_events->num_ready = 0;
+	return LIBUSB_SUCCESS;
+}
diff --git a/libusb/os/events_windows.h b/libusb/os/events_windows.h
new file mode 100644
index 0000000..0c5e0b0
--- /dev/null
+++ b/libusb/os/events_windows.h
@@ -0,0 +1,46 @@
+/*
+ * libusb event abstraction on Microsoft Windows
+ *
+ * Copyright © 2020 Chris Dickens <christopher.a.dickens@gmail.com>
+ *
+ * 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 LIBUSB_EVENTS_WINDOWS_H
+#define LIBUSB_EVENTS_WINDOWS_H
+
+typedef HANDLE usbi_os_handle_t;
+#define USBI_OS_HANDLE_FORMAT_STRING	"HANDLE %p"
+
+typedef struct usbi_event {
+	HANDLE hEvent;
+} usbi_event_t;
+#define USBI_EVENT_OS_HANDLE(e)	((e)->hEvent)
+#define USBI_EVENT_POLL_EVENTS	0
+#define USBI_INVALID_EVENT	{ INVALID_HANDLE_VALUE }
+
+#define HAVE_OS_TIMER 1
+typedef struct usbi_timer {
+	HANDLE hTimer;
+} usbi_timer_t;
+#define USBI_TIMER_OS_HANDLE(t)	((t)->hTimer)
+#define USBI_TIMER_POLL_EVENTS	0
+
+static inline int usbi_timer_valid(usbi_timer_t *timer)
+{
+	return timer->hTimer != NULL;
+}
+
+#endif
diff --git a/libusb/os/linux_netlink.c b/libusb/os/linux_netlink.c
index 9917df2..77c83c5 100644
--- a/libusb/os/linux_netlink.c
+++ b/libusb/os/linux_netlink.c
@@ -48,7 +48,7 @@
 #endif
 
 static int linux_netlink_socket = -1;
-static int netlink_control_pipe[2] = { -1, -1 };
+static usbi_event_t netlink_control_event = USBI_INVALID_EVENT;
 static pthread_t libusb_linux_event_thread;
 
 static void *linux_netlink_event_thread_main(void *arg);
@@ -125,25 +125,23 @@
 		goto err_close_socket;
 	}
 
-	ret = usbi_pipe(netlink_control_pipe);
+	ret = usbi_create_event(&netlink_control_event);
 	if (ret) {
-		usbi_err(NULL, "failed to create netlink control pipe");
+		usbi_err(NULL, "failed to create netlink control event");
 		goto err_close_socket;
 	}
 
 	ret = pthread_create(&libusb_linux_event_thread, NULL, linux_netlink_event_thread_main, NULL);
 	if (ret != 0) {
 		usbi_err(NULL, "failed to create netlink event thread (%d)", ret);
-		goto err_close_pipe;
+		goto err_destroy_event;
 	}
 
 	return LIBUSB_SUCCESS;
 
-err_close_pipe:
-	close(netlink_control_pipe[0]);
-	close(netlink_control_pipe[1]);
-	netlink_control_pipe[0] = -1;
-	netlink_control_pipe[1] = -1;
+err_destroy_event:
+	usbi_destroy_event(&netlink_control_event);
+	netlink_control_event = (usbi_event_t)USBI_INVALID_EVENT;
 err_close_socket:
 	close(linux_netlink_socket);
 	linux_netlink_socket = -1;
@@ -153,28 +151,23 @@
 
 int linux_netlink_stop_event_monitor(void)
 {
-	char dummy = 1;
-	ssize_t r;
+	int ret;
 
 	assert(linux_netlink_socket != -1);
 
-	/* Write some dummy data to the control pipe and
-	 * wait for the thread to exit */
-	r = write(netlink_control_pipe[1], &dummy, sizeof(dummy));
-	if (r <= 0)
-		usbi_warn(NULL, "netlink control pipe signal failed");
+	/* Signal the control event and wait for the thread to exit */
+	usbi_signal_event(&netlink_control_event);
 
-	pthread_join(libusb_linux_event_thread, NULL);
+	ret = pthread_join(libusb_linux_event_thread, NULL);
+	if (ret)
+		usbi_warn(NULL, "failed to join netlink event thread (%d)", ret);
+
+	usbi_destroy_event(&netlink_control_event);
+	netlink_control_event = (usbi_event_t)USBI_INVALID_EVENT;
 
 	close(linux_netlink_socket);
 	linux_netlink_socket = -1;
 
-	/* close and reset control pipe */
-	close(netlink_control_pipe[0]);
-	close(netlink_control_pipe[1]);
-	netlink_control_pipe[0] = -1;
-	netlink_control_pipe[1] = -1;
-
 	return LIBUSB_SUCCESS;
 }
 
@@ -353,15 +346,13 @@
 
 static void *linux_netlink_event_thread_main(void *arg)
 {
-	char dummy;
-	int r;
-	ssize_t nb;
 	struct pollfd fds[] = {
-		{ .fd = netlink_control_pipe[0],
-		  .events = POLLIN },
+		{ .fd = USBI_EVENT_OS_HANDLE(&netlink_control_event),
+		  .events = USBI_EVENT_POLL_EVENTS },
 		{ .fd = linux_netlink_socket,
 		  .events = POLLIN },
 	};
+	int r;
 
 	UNUSED(arg);
 
@@ -373,19 +364,20 @@
 
 	usbi_dbg("netlink event thread entering");
 
-	while ((r = poll(fds, 2, -1)) >= 0 || errno == EINTR) {
-		if (r < 0) {
-			/* temporary failure */
-			continue;
-		}
-		if (fds[0].revents & POLLIN) {
-			/* activity on control pipe, read the byte and exit */
-			nb = read(netlink_control_pipe[0], &dummy, sizeof(dummy));
-			if (nb <= 0)
-				usbi_warn(NULL, "netlink control pipe read failed");
+	while (1) {
+		r = poll(fds, 2, -1);
+		if (r == -1) {
+			/* check for temporary failure */
+			if (errno == EINTR)
+				continue;
+			usbi_err(NULL, "poll() failed, errno=%d", errno);
 			break;
 		}
-		if (fds[1].revents & POLLIN) {
+		if (fds[0].revents) {
+			/* activity on control event, exit */
+			break;
+		}
+		if (fds[1].revents) {
 			usbi_mutex_static_lock(&linux_hotplug_lock);
 			linux_netlink_read_message();
 			usbi_mutex_static_unlock(&linux_hotplug_lock);
diff --git a/libusb/os/linux_udev.c b/libusb/os/linux_udev.c
index e8fb198..beb2f05 100644
--- a/libusb/os/linux_udev.c
+++ b/libusb/os/linux_udev.c
@@ -34,7 +34,7 @@
 /* udev context */
 static struct udev *udev_ctx = NULL;
 static int udev_monitor_fd = -1;
-static int udev_control_pipe[2] = {-1, -1};
+static usbi_event_t udev_control_event = USBI_INVALID_EVENT;
 static struct udev_monitor *udev_monitor = NULL;
 static pthread_t linux_event_thread;
 
@@ -100,23 +100,23 @@
 		}
 	}
 
-	r = usbi_pipe(udev_control_pipe);
+	r = usbi_create_event(&udev_control_event);
 	if (r) {
-		usbi_err(NULL, "could not create udev control pipe");
+		usbi_err(NULL, "failed to create udev control event");
 		goto err_free_monitor;
 	}
 
 	r = pthread_create(&linux_event_thread, NULL, linux_udev_event_thread_main, NULL);
 	if (r) {
-		usbi_err(NULL, "creating hotplug event thread (%d)", r);
-		goto err_close_pipe;
+		usbi_err(NULL, "failed to create hotplug event thread (%d)", r);
+		goto err_destroy_event;
 	}
 
 	return LIBUSB_SUCCESS;
 
-err_close_pipe:
-	close(udev_control_pipe[0]);
-	close(udev_control_pipe[1]);
+err_destroy_event:
+	usbi_destroy_event(&udev_control_event);
+	udev_control_event = (usbi_event_t)USBI_INVALID_EVENT;
 err_free_monitor:
 	udev_monitor_unref(udev_monitor);
 	udev_monitor = NULL;
@@ -130,20 +130,21 @@
 
 int linux_udev_stop_event_monitor(void)
 {
-	char dummy = 1;
-	ssize_t r;
+	int r;
 
 	assert(udev_ctx != NULL);
 	assert(udev_monitor != NULL);
 	assert(udev_monitor_fd != -1);
 
-	/* Write some dummy data to the control pipe and
-	 * wait for the thread to exit */
-	r = write(udev_control_pipe[1], &dummy, sizeof(dummy));
-	if (r <= 0) {
-		usbi_warn(NULL, "udev control pipe signal failed");
-	}
-	pthread_join(linux_event_thread, NULL);
+	/* Signal the control event and wait for the thread to exit */
+	usbi_signal_event(&udev_control_event);
+
+	r = pthread_join(linux_event_thread, NULL);
+	if (r)
+		usbi_warn(NULL, "failed to join hotplug event thread (%d)", r);
+
+	usbi_destroy_event(&udev_control_event);
+	udev_control_event = (usbi_event_t)USBI_INVALID_EVENT;
 
 	/* Release the udev monitor */
 	udev_monitor_unref(udev_monitor);
@@ -154,27 +155,19 @@
 	udev_unref(udev_ctx);
 	udev_ctx = NULL;
 
-	/* close and reset control pipe */
-	close(udev_control_pipe[0]);
-	close(udev_control_pipe[1]);
-	udev_control_pipe[0] = -1;
-	udev_control_pipe[1] = -1;
-
 	return LIBUSB_SUCCESS;
 }
 
 static void *linux_udev_event_thread_main(void *arg)
 {
-	char dummy;
-	int r;
-	ssize_t nb;
-	struct udev_device *udev_dev;
 	struct pollfd fds[] = {
-		{.fd = udev_control_pipe[0],
-		 .events = POLLIN},
-		{.fd = udev_monitor_fd,
-		 .events = POLLIN},
+		{ .fd = USBI_EVENT_OS_HANDLE(&udev_control_event),
+		  .events = USBI_EVENT_POLL_EVENTS },
+		{ .fd = udev_monitor_fd,
+		  .events = POLLIN },
 	};
+	struct udev_device *udev_dev;
+	int r;
 
 	UNUSED(arg);
 
@@ -186,20 +179,20 @@
 
 	usbi_dbg("udev event thread entering");
 
-	while ((r = poll(fds, 2, -1)) >= 0 || errno == EINTR) {
-		if (r < 0) {
-			/* temporary failure */
-			continue;
-		}
-		if (fds[0].revents & POLLIN) {
-			/* activity on control pipe, read the byte and exit */
-			nb = read(udev_control_pipe[0], &dummy, sizeof(dummy));
-			if (nb <= 0) {
-				usbi_warn(NULL, "udev control pipe read failed");
-			}
+	while (1) {
+		r = poll(fds, 2, -1);
+		if (r == -1) {
+			/* check for temporary failure */
+			if (errno == EINTR)
+				continue;
+			usbi_err(NULL, "poll() failed, errno=%d", errno);
 			break;
 		}
-		if (fds[1].revents & POLLIN) {
+		if (fds[0].revents) {
+			/* activity on control event, exit */
+			break;
+		}
+		if (fds[1].revents) {
 			usbi_mutex_static_lock(&linux_hotplug_lock);
 			udev_dev = udev_monitor_receive_device(udev_monitor);
 			if (udev_dev)
diff --git a/libusb/os/linux_usbfs.c b/libusb/os/linux_usbfs.c
index 1e727ef..c2980d0 100644
--- a/libusb/os/linux_usbfs.c
+++ b/libusb/os/linux_usbfs.c
@@ -36,6 +36,7 @@
 #include <sys/mman.h>
 #include <sys/utsname.h>
 #include <sys/vfs.h>
+#include <unistd.h>
 
 /* sysfs vs usbfs:
  * opening a usbfs node causes the device to be resumed, so we attempt to
@@ -1266,7 +1267,7 @@
 		hpriv->caps = USBFS_CAP_BULK_CONTINUATION;
 	}
 
-	return usbi_add_pollfd(HANDLE_CTX(handle), hpriv->fd, POLLOUT);
+	return usbi_add_event_source(HANDLE_CTX(handle), hpriv->fd, POLLOUT);
 }
 
 static int op_wrap_sys_device(struct libusb_context *ctx,
@@ -1352,7 +1353,7 @@
 
 	/* fd may have already been removed by POLLERR condition in op_handle_events() */
 	if (!hpriv->fd_removed)
-		usbi_remove_pollfd(HANDLE_CTX(dev_handle), hpriv->fd);
+		usbi_remove_event_source(HANDLE_CTX(dev_handle), hpriv->fd);
 	if (!hpriv->fd_keep)
 		close(hpriv->fd);
 }
@@ -2614,13 +2615,14 @@
 }
 
 static int op_handle_events(struct libusb_context *ctx,
-	struct pollfd *fds, usbi_nfds_t nfds, int num_ready)
+	void *event_data, unsigned int count, unsigned int num_ready)
 {
-	usbi_nfds_t n;
+	struct pollfd *fds = event_data;
+	unsigned int n;
 	int r;
 
 	usbi_mutex_lock(&ctx->open_devs_lock);
-	for (n = 0; n < nfds && num_ready > 0; n++) {
+	for (n = 0; n < count && num_ready > 0; n++) {
 		struct pollfd *pollfd = &fds[n];
 		struct libusb_device_handle *handle;
 		struct linux_device_handle_priv *hpriv = NULL;
@@ -2645,7 +2647,7 @@
 			/* remove the fd from the pollfd set so that it doesn't continuously
 			 * trigger an event, and flag that it has been removed so op_close()
 			 * doesn't try to remove it a second time */
-			usbi_remove_pollfd(HANDLE_CTX(handle), hpriv->fd);
+			usbi_remove_event_source(HANDLE_CTX(handle), hpriv->fd);
 			hpriv->fd_removed = 1;
 
 			/* device will still be marked as attached if hotplug monitor thread
diff --git a/libusb/os/poll_posix.c b/libusb/os/poll_posix.c
deleted file mode 100644
index 93e9bee..0000000
--- a/libusb/os/poll_posix.c
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * poll_posix: poll compatibility wrapper for POSIX systems
- * Copyright © 2013 RealVNC Ltd.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- */
-
-#include "libusbi.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <unistd.h>
-
-int usbi_pipe(int pipefd[2])
-{
-#if defined(HAVE_PIPE2)
-	int ret = pipe2(pipefd, O_CLOEXEC);
-#else
-	int ret = pipe(pipefd);
-#endif
-
-	if (ret != 0) {
-		usbi_err(NULL, "failed to create pipe, errno=%d", errno);
-		return ret;
-	}
-
-#if !defined(HAVE_PIPE2) && defined(FD_CLOEXEC)
-	ret = fcntl(pipefd[0], F_GETFD);
-	if (ret == -1) {
-		usbi_err(NULL, "failed to get pipe fd flags, errno=%d", errno);
-		goto err_close_pipe;
-	}
-	ret = fcntl(pipefd[0], F_SETFD, ret | FD_CLOEXEC);
-	if (ret == -1) {
-		usbi_err(NULL, "failed to set pipe fd flags, errno=%d", errno);
-		goto err_close_pipe;
-	}
-
-	ret = fcntl(pipefd[1], F_GETFD);
-	if (ret == -1) {
-		usbi_err(NULL, "failed to get pipe fd flags, errno=%d", errno);
-		goto err_close_pipe;
-	}
-	ret = fcntl(pipefd[1], F_SETFD, ret | FD_CLOEXEC);
-	if (ret == -1) {
-		usbi_err(NULL, "failed to set pipe fd flags, errno=%d", errno);
-		goto err_close_pipe;
-	}
-#endif
-
-	ret = fcntl(pipefd[1], F_GETFL);
-	if (ret == -1) {
-		usbi_err(NULL, "failed to get pipe fd status flags, errno=%d", errno);
-		goto err_close_pipe;
-	}
-	ret = fcntl(pipefd[1], F_SETFL, ret | O_NONBLOCK);
-	if (ret == -1) {
-		usbi_err(NULL, "failed to set pipe fd status flags, errno=%d", errno);
-		goto err_close_pipe;
-	}
-
-	return 0;
-
-err_close_pipe:
-	close(pipefd[0]);
-	close(pipefd[1]);
-	return ret;
-}
diff --git a/libusb/os/poll_posix.h b/libusb/os/poll_posix.h
deleted file mode 100644
index 48c4904..0000000
--- a/libusb/os/poll_posix.h
+++ /dev/null
@@ -1,20 +0,0 @@
-#ifndef LIBUSB_POLL_POSIX_H
-#define LIBUSB_POLL_POSIX_H
-
-#include <poll.h>
-#include <unistd.h>
-
-#ifdef HAVE_NFDS_T
-typedef nfds_t usbi_nfds_t;
-#else
-typedef unsigned int usbi_nfds_t;
-#endif
-
-#define usbi_write	write
-#define usbi_read	read
-#define usbi_close	close
-#define usbi_poll	poll
-
-int usbi_pipe(int pipefd[2]);
-
-#endif /* LIBUSB_POLL_POSIX_H */
diff --git a/libusb/os/poll_windows.c b/libusb/os/poll_windows.c
deleted file mode 100644
index c2bc105..0000000
--- a/libusb/os/poll_windows.c
+++ /dev/null
@@ -1,502 +0,0 @@
-/*
- * poll_windows: poll compatibility wrapper for Windows
- * Copyright © 2017 Chris Dickens <christopher.a.dickens@gmail.com>
- *
- * 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
- *
- */
-
-/*
- * poll() and pipe() Windows compatibility layer for libusb 1.0
- *
- * The pipe pollable synchronous I/O works using the overlapped event associated
- * with a fake pipe. The read/write functions are only meant to be used in that
- * context.
- */
-
-#include "libusbi.h"
-#include "windows_common.h"
-
-#include <errno.h>
-#include <intrin.h>
-#include <malloc.h>
-#include <stdbool.h>
-#include <stdlib.h>
-
-
-// private data
-struct file_descriptor {
-	LONG refcount;
-	OVERLAPPED overlapped;
-};
-
-static usbi_mutex_static_t fd_table_lock = USBI_MUTEX_INITIALIZER;
-
-#define BITS_PER_BYTE			8
-#define BITMAP_BITS_PER_WORD		(sizeof(unsigned long) * BITS_PER_BYTE)
-#define FD_TABLE_INCR_SIZE		64
-
-static struct file_descriptor **fd_table;
-static unsigned long *fd_table_bitmap;
-static unsigned int fd_table_size;
-static unsigned int fd_count;
-
-#define return_with_errno(err)		\
-	do {				\
-		errno = (err);		\
-		return -1;		\
-	} while (0)
-
-static struct file_descriptor *alloc_fd(LONG refcount)
-{
-	struct file_descriptor *fd = calloc(1, sizeof(*fd));
-
-	if (fd == NULL)
-		return NULL;
-	fd->overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
-	if (fd->overlapped.hEvent == NULL) {
-		free(fd);
-		return NULL;
-	}
-	fd->refcount = refcount;
-	return fd;
-}
-
-static struct file_descriptor *get_fd(unsigned int _fd, bool ref)
-{
-	struct file_descriptor *fd = NULL;
-
-	if (_fd < fd_table_size)
-		fd = fd_table[_fd];
-	if (fd != NULL && ref)
-		InterlockedIncrement(&fd->refcount);
-
-	return fd;
-}
-
-static void put_fd(struct file_descriptor *fd)
-{
-	if (InterlockedDecrement(&fd->refcount) == 0L) {
-		CloseHandle(fd->overlapped.hEvent);
-		free(fd);
-	}
-}
-
-static int install_fd(struct file_descriptor *fd)
-{
-	unsigned int n;
-
-	if (fd_count == fd_table_size) {
-		struct file_descriptor **new_table;
-		unsigned long *new_bitmap;
-
-		// Need to expand the fd table and bitmap
-		new_table = realloc(fd_table, (fd_table_size + FD_TABLE_INCR_SIZE) * sizeof(*new_table));
-		if (new_table == NULL)
-			return -ENOMEM;
-		memset(new_table + fd_table_size, 0, FD_TABLE_INCR_SIZE * sizeof(*new_table));
-		fd_table = new_table;
-
-		new_bitmap = realloc(fd_table_bitmap, (fd_table_size + FD_TABLE_INCR_SIZE) / BITS_PER_BYTE);
-		if (new_bitmap == NULL)
-			return -ENOMEM;
-		memset(new_bitmap + (fd_table_size / BITMAP_BITS_PER_WORD), 0, FD_TABLE_INCR_SIZE / BITS_PER_BYTE);
-		fd_table_bitmap = new_bitmap;
-
-		fd_table_size += FD_TABLE_INCR_SIZE;
-		assert(fd_table_size < (unsigned int)INT_MAX);
-	}
-
-	for (n = 0; n < fd_table_size; n += BITMAP_BITS_PER_WORD) {
-		unsigned int idx = n / BITMAP_BITS_PER_WORD;
-		ULONG mask, pos = 0U;
-		unsigned char nonzero;
-
-		mask = ~fd_table_bitmap[idx];
-		if (mask == 0U)
-			continue;
-
-		nonzero = _BitScanForward(&pos, mask);
-		assert(nonzero);
-		fd_table_bitmap[idx] |= 1U << pos;
-		n += pos;
-		break;
-	}
-
-	assert(n < fd_table_size);
-	assert(fd_table[n] == NULL);
-	fd_table[n] = fd;
-	fd_count++;
-
-	return n;
-}
-
-static void remove_fd(unsigned int pos)
-{
-	assert(fd_table[pos] != NULL);
-	fd_table[pos] = NULL;
-	fd_table_bitmap[pos / BITMAP_BITS_PER_WORD] &= ~(1U << (pos % BITMAP_BITS_PER_WORD));
-	fd_count--;
-	if (fd_count == 0) {
-		free(fd_table);
-		free(fd_table_bitmap);
-		fd_table = NULL;
-		fd_table_bitmap = NULL;
-		fd_table_size = 0;
-	}
-}
-
-struct wait_thread_data {
-	HANDLE thread;
-	HANDLE handles[MAXIMUM_WAIT_OBJECTS];
-	DWORD num_handles;
-	DWORD error;
-};
-
-static DWORD WINAPI WaitThread(LPVOID lpParam)
-{
-	struct wait_thread_data *thread_data = lpParam;
-	HANDLE notify_event = thread_data->handles[0];
-	DWORD status;
-
-	status = WaitForMultipleObjects(thread_data->num_handles, thread_data->handles, FALSE, INFINITE);
-	if (status < (WAIT_OBJECT_0 + thread_data->num_handles)) {
-		if (status > WAIT_OBJECT_0) {
-			// This will wake up all the other waiting threads
-			SetEvent(notify_event);
-		}
-		thread_data->error = 0;
-	} else {
-		assert(status == WAIT_FAILED);
-		thread_data->error = (status == WAIT_FAILED) ? GetLastError() : ERROR_CAN_NOT_COMPLETE;
-	}
-
-	return 0;
-}
-
-static DWORD poll_wait(const HANDLE *wait_handles, DWORD num_wait_handles, DWORD timeout)
-{
-	struct wait_thread_data *thread_data;
-	HANDLE notify_event;
-	HANDLE *handles;
-	int n, num_threads;
-	DWORD error, status;
-
-	if (num_wait_handles <= MAXIMUM_WAIT_OBJECTS)
-		return WaitForMultipleObjects(num_wait_handles, wait_handles, FALSE, timeout);
-
-	// To wait on more than MAXIMUM_WAIT_OBJECTS, each thread (including the
-	// current thread) will wait on an event and (MAXIMUM_WAIT_OBJECTS - 1)
-	// HANDLEs.  The event is shared amongst all threads so that any thread
-	// that returns from a WaitForMultipleObjects() call will set the event
-	// and wake up all the other threads.
-	notify_event = CreateEvent(NULL, FALSE, FALSE, NULL);
-	if (notify_event == NULL)
-		return WAIT_FAILED;
-
-	num_threads = 1 + (num_wait_handles - MAXIMUM_WAIT_OBJECTS - 1) / (MAXIMUM_WAIT_OBJECTS - 1);
-	thread_data = malloc(num_threads * sizeof(*thread_data));
-	if (thread_data == NULL) {
-		CloseHandle(notify_event);
-		SetLastError(ERROR_OUTOFMEMORY);
-		return WAIT_FAILED;
-	}
-
-	handles = _alloca(MAXIMUM_WAIT_OBJECTS * sizeof(HANDLE));
-	handles[0] = notify_event;
-	memcpy(handles + 1, wait_handles, (MAXIMUM_WAIT_OBJECTS - 1) * sizeof(HANDLE));
-	wait_handles += MAXIMUM_WAIT_OBJECTS - 1;
-	num_wait_handles -= MAXIMUM_WAIT_OBJECTS - 1;
-
-	for (n = 0; n < num_threads; n++) {
-		DWORD copy_size = MIN(num_wait_handles, MAXIMUM_WAIT_OBJECTS - 1);
-
-		thread_data[n].handles[0] = notify_event;
-		memcpy(thread_data[n].handles + 1, wait_handles, copy_size * sizeof(HANDLE));
-		thread_data[n].num_handles = copy_size + 1;
-
-		// Create the thread that will wait on these HANDLEs
-		thread_data[n].thread = CreateThread(NULL, 0, WaitThread, &thread_data[n], 0, NULL);
-		if (thread_data[n].thread == NULL) {
-			thread_data[n].error = GetLastError();
-			SetEvent(notify_event);
-			num_threads = n + 1;
-			break;
-		}
-
-		wait_handles += copy_size;
-		num_wait_handles -= copy_size;
-	}
-
-	status = WaitForMultipleObjects(MAXIMUM_WAIT_OBJECTS, handles, FALSE, timeout);
-	if (status < (WAIT_OBJECT_0 + MAXIMUM_WAIT_OBJECTS)) {
-		if (status > WAIT_OBJECT_0) {
-			// Wake up all the waiting threads
-			SetEvent(notify_event);
-			status = WAIT_OBJECT_0;
-		}
-		error = 0;
-	} else if (status == WAIT_TIMEOUT) {
-		// Wake up all the waiting threads
-		SetEvent(notify_event);
-		error = 0;
-	} else {
-		assert(status == WAIT_FAILED);
-		error = (status == WAIT_FAILED) ? GetLastError() : ERROR_CAN_NOT_COMPLETE;
-	}
-
-	for (n = 0; n < num_threads; n++) {
-		if (thread_data[n].thread != NULL) {
-			if (WaitForSingleObject(thread_data[n].thread, INFINITE) != WAIT_OBJECT_0)
-				usbi_err(NULL, "WaitForSingleObject() failed: %lu", ULONG_CAST(GetLastError()));
-			CloseHandle(thread_data[n].thread);
-		}
-		if (thread_data[n].error) {
-			usbi_err(NULL, "wait thread %d had error %lu\n", n, ULONG_CAST(thread_data[n].error));
-			error = thread_data[n].error;
-			status = WAIT_FAILED;
-		}
-	}
-
-	free(thread_data);
-
-	CloseHandle(notify_event);
-
-	if (status == WAIT_FAILED)
-		SetLastError(error);
-
-	return status;
-}
-
-/*
- * POSIX poll equivalent, using Windows OVERLAPPED
- * Currently, this function only accepts one of POLLIN or POLLOUT per fd
- * (but you can create multiple fds from the same handle for read and write)
- */
-int usbi_poll(struct pollfd *fds, usbi_nfds_t nfds, int timeout)
-{
-	struct file_descriptor **fds_array;
-	HANDLE *handles_array;
-	struct file_descriptor *fd;
-	usbi_nfds_t n;
-	int nready;
-
-	if (nfds <= MAXIMUM_WAIT_OBJECTS) {
-		fds_array = _alloca(nfds * sizeof(*fds_array));
-		handles_array = _alloca(nfds * sizeof(*handles_array));
-	} else {
-		fds_array = malloc(nfds * sizeof(*fds_array));
-		if (fds_array == NULL)
-			return_with_errno(ENOMEM);
-		handles_array = malloc(nfds * sizeof(*handles_array));
-		if (handles_array == NULL) {
-			free(fds_array);
-			return_with_errno(ENOMEM);
-		}
-	}
-
-	usbi_mutex_static_lock(&fd_table_lock);
-	for (n = 0; n < nfds; n++) {
-		struct pollfd *pfd = &fds[n];
-
-		// Keep it simple - only allow either POLLIN *or* POLLOUT
-		assert((pfd->events == POLLIN) || (pfd->events == POLLOUT));
-		if ((pfd->events != POLLIN) && (pfd->events != POLLOUT)) {
-			fds_array[n] = NULL;
-			continue;
-		}
-
-		// All file descriptors must be valid
-		fd = get_fd(pfd->fd, true);
-		assert(fd != NULL);
-		if (fd == NULL) {
-			fds_array[n] = NULL;
-			continue;
-		}
-
-		// We hold a reference to fd for the duration of usbi_poll()
-		fds_array[n] = fd;
-		handles_array[n] = fd->overlapped.hEvent;
-	}
-	usbi_mutex_static_unlock(&fd_table_lock);
-
-	nready = 0;
-	while (nready == 0) {
-		DWORD ret;
-
-		// Check all fds for events
-		for (n = 0; n < nfds; n++) {
-			fd = fds_array[n];
-			if (fd == NULL) {
-				fds[n].revents = POLLNVAL;
-				nready++;
-			} else if (HasOverlappedIoCompleted(&fd->overlapped)) {
-				fds[n].revents = fds[n].events;
-				nready++;
-			} else {
-				fds[n].revents = 0;
-			}
-		}
-
-		if ((nready != 0) || (timeout == 0))
-			break;
-
-		// Wait for any of the events to trigger
-		ret = poll_wait(handles_array, nfds, (timeout < 0) ? INFINITE : (DWORD)timeout);
-		if (ret == WAIT_TIMEOUT) {
-			assert(timeout > 0);
-			timeout = 0;
-		} else if (ret == WAIT_FAILED) {
-			usbi_err(NULL, "WaitForMultipleObjects failed: %lu", ULONG_CAST(GetLastError()));
-			errno = EIO;
-			nready = -1;
-		}
-	}
-
-	for (n = 0; n < nfds; n++) {
-		if (fds_array[n] != NULL)
-			put_fd(fds_array[n]);
-	}
-
-	if (nfds > MAXIMUM_WAIT_OBJECTS) {
-		free(handles_array);
-		free(fds_array);
-	}
-
-	return nready;
-}
-
-/*
- * close a fake file descriptor
- */
-int usbi_close(int _fd)
-{
-	struct file_descriptor *fd;
-
-	usbi_mutex_static_lock(&fd_table_lock);
-	fd = get_fd(_fd, false);
-	if (fd != NULL)
-		remove_fd(_fd);
-	usbi_mutex_static_unlock(&fd_table_lock);
-
-	if (fd == NULL)
-		return_with_errno(EBADF);
-
-	put_fd(fd);
-
-	return 0;
-}
-
-/*
-* Create a fake pipe.
-* As libusb only uses pipes for signaling, all we need from a pipe is an
-* event. To that extent, we create a single wfd and overlapped as a means
-* to access that event.
-*/
-int usbi_pipe(int filedes[2])
-{
-	struct file_descriptor *fd;
-	int r_fd, w_fd;
-	int error = 0;
-
-	fd = alloc_fd(2);
-	if (fd == NULL)
-		return_with_errno(ENOMEM);
-
-	fd->overlapped.Internal = STATUS_PENDING;
-
-	usbi_mutex_static_lock(&fd_table_lock);
-	r_fd = install_fd(fd);
-	if (r_fd >= 0) {
-		w_fd = install_fd(fd);
-		if (w_fd < 0) {
-			remove_fd(r_fd);
-			error = w_fd;
-		}
-	} else {
-		error = r_fd;
-		w_fd = -1; // Keep compiler happy
-	}
-	usbi_mutex_static_unlock(&fd_table_lock);
-
-	if (error) {
-		CloseHandle(fd->overlapped.hEvent);
-		free(fd);
-		return_with_errno(error);
-	}
-
-	filedes[0] = r_fd;
-	filedes[1] = w_fd;
-
-	return 0;
-}
-
-/*
- * synchronous write for fake "pipe" signaling
- */
-ssize_t usbi_write(int _fd, const void *buf, size_t count)
-{
-	struct file_descriptor *fd;
-
-	UNUSED(buf);
-
-	if (count != sizeof(unsigned char)) {
-		usbi_err(NULL, "this function should only used for signaling");
-		return_with_errno(EINVAL);
-	}
-
-	usbi_mutex_static_lock(&fd_table_lock);
-	fd = get_fd(_fd, false);
-	if (fd != NULL) {
-		assert(fd->overlapped.Internal == STATUS_PENDING);
-		fd->overlapped.Internal = STATUS_WAIT_0;
-		SetEvent(fd->overlapped.hEvent);
-	}
-	usbi_mutex_static_unlock(&fd_table_lock);
-
-	if (fd == NULL)
-		return_with_errno(EBADF);
-
-	return sizeof(unsigned char);
-}
-
-/*
- * synchronous read for fake "pipe" signaling
- */
-ssize_t usbi_read(int _fd, void *buf, size_t count)
-{
-	struct file_descriptor *fd;
-
-	UNUSED(buf);
-
-	if (count != sizeof(unsigned char)) {
-		usbi_err(NULL, "this function should only used for signaling");
-		return_with_errno(EINVAL);
-	}
-
-	usbi_mutex_static_lock(&fd_table_lock);
-	fd = get_fd(_fd, false);
-	if (fd != NULL) {
-		assert(fd->overlapped.Internal == STATUS_WAIT_0);
-		fd->overlapped.Internal = STATUS_PENDING;
-		ResetEvent(fd->overlapped.hEvent);
-	}
-	usbi_mutex_static_unlock(&fd_table_lock);
-
-	if (fd == NULL)
-		return_with_errno(EBADF);
-
-	return sizeof(unsigned char);
-}
diff --git a/libusb/os/poll_windows.h b/libusb/os/poll_windows.h
deleted file mode 100644
index 14a5b27..0000000
--- a/libusb/os/poll_windows.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Windows compat: POSIX compatibility wrapper
- * Copyright © 2012-2013 RealVNC Ltd.
- * Copyright © 2009-2010 Pete Batard <pete@akeo.ie>
- * Copyright © 2016-2018 Chris Dickens <christopher.a.dickens@gmail.com>
- * With contributions from Michael Plante, Orin Eman et al.
- * Parts of poll implementation from libusb-win32, by Stephan Meyer et al.
- *
- * 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 LIBUSB_POLL_WINDOWS_H
-#define LIBUSB_POLL_WINDOWS_H
-
-#define POLLIN		0x0001	/* There is data to read */
-#define POLLPRI		0x0002	/* There is urgent data to read */
-#define POLLOUT		0x0004	/* Writing now will not block */
-#define POLLERR		0x0008	/* Error condition */
-#define POLLHUP		0x0010	/* Hung up */
-#define POLLNVAL	0x0020	/* Invalid request: fd not open */
-
-typedef unsigned int usbi_nfds_t;
-
-struct pollfd {
-	int fd;		/* file descriptor */
-	short events;	/* requested events */
-	short revents;	/* returned events */
-};
-
-int usbi_pipe(int pipefd[2]);
-int usbi_poll(struct pollfd *fds, usbi_nfds_t nfds, int timeout);
-ssize_t usbi_write(int fd, const void *buf, size_t count);
-ssize_t usbi_read(int fd, void *buf, size_t count);
-int usbi_close(int fd);
-
-#endif
diff --git a/libusb/os/windows_winusb.c b/libusb/os/windows_winusb.c
index 04dda4a..fa6721e 100644
--- a/libusb/os/windows_winusb.c
+++ b/libusb/os/windows_winusb.c
@@ -467,7 +467,7 @@
 /*
  * Open a device and associate the HANDLE with the context's I/O completion port
  */
-HANDLE windows_open(struct libusb_device *dev, const char *path, DWORD access)
+static HANDLE windows_open(struct libusb_device *dev, const char *path, DWORD access)
 {
 	struct libusb_context *ctx = DEVICE_CTX(dev);
 	struct windows_context_priv *priv = usbi_get_context_priv(ctx);
diff --git a/libusb/version_nano.h b/libusb/version_nano.h
index e7b7f08..bf72b26 100644
--- a/libusb/version_nano.h
+++ b/libusb/version_nano.h
@@ -1 +1 @@
-#define LIBUSB_NANO 11531
+#define LIBUSB_NANO 11532
diff --git a/msvc/config.h b/msvc/config.h
index 58d1ed7..2f6eaf0 100644
--- a/msvc/config.h
+++ b/msvc/config.h
@@ -47,8 +47,8 @@
 /* Define to 1 to enable message logging. */
 #define ENABLE_LOGGING 1
 
-/* Define to 1 if using the Windows poll() implementation. */
-#define POLL_WINDOWS 1
+/* Define to 1 if using the Windows events abstraction. */
+#define EVENTS_WINDOWS 1
 
 /* Define to 1 if using Windows threads. */
 #define THREADS_WINDOWS 1
diff --git a/msvc/libusb_dll_2013.vcxproj b/msvc/libusb_dll_2013.vcxproj
index b858f77..56ffd75 100644
--- a/msvc/libusb_dll_2013.vcxproj
+++ b/msvc/libusb_dll_2013.vcxproj
@@ -70,9 +70,9 @@
   <ItemGroup>
     <ClCompile Include="..\libusb\core.c" />
     <ClCompile Include="..\libusb\descriptor.c" />
+    <ClCompile Include="..\libusb\os\events_windows.c" />
     <ClCompile Include="..\libusb\hotplug.c" />
     <ClCompile Include="..\libusb\io.c" />
-    <ClCompile Include="..\libusb\os\poll_windows.c" />
     <ClCompile Include="..\libusb\strerror.c" />
     <ClCompile Include="..\libusb\sync.c" />
     <ClCompile Include="..\libusb\os\threads_windows.c" />
@@ -82,10 +82,10 @@
   </ItemGroup>
   <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\poll_windows.h" />
     <ClInclude Include="..\libusb\os\threads_windows.h" />
     <ClInclude Include="..\libusb\version.h" />
     <ClInclude Include="..\libusb\version_nano.h" />
diff --git a/msvc/libusb_dll_2013.vcxproj.filters b/msvc/libusb_dll_2013.vcxproj.filters
index 2e6cc93..8da28e3 100644
--- a/msvc/libusb_dll_2013.vcxproj.filters
+++ b/msvc/libusb_dll_2013.vcxproj.filters
@@ -18,6 +18,9 @@
     <ClInclude Include=".\config.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="..\libusb\os\events_windows.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="..\libusb\hotplug.h">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -27,9 +30,6 @@
     <ClInclude Include="..\libusb\libusbi.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="..\libusb\os\poll_windows.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
     <ClInclude Include="..\libusb\os\threads_windows.h">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -61,15 +61,15 @@
     <ClCompile Include="..\libusb\descriptor.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\libusb\os\events_windows.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="..\libusb\hotplug.c">
       <Filter>Source Files</Filter>
     </ClCompile>
     <ClCompile Include="..\libusb\io.c">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\libusb\os\poll_windows.c">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\libusb\strerror.c">
       <Filter>Source Files</Filter>
     </ClCompile>
diff --git a/msvc/libusb_dll_2015.vcxproj b/msvc/libusb_dll_2015.vcxproj
index 4a73ef7..d2c850d 100644
--- a/msvc/libusb_dll_2015.vcxproj
+++ b/msvc/libusb_dll_2015.vcxproj
@@ -71,9 +71,9 @@
   <ItemGroup>
     <ClCompile Include="..\libusb\core.c" />
     <ClCompile Include="..\libusb\descriptor.c" />
+    <ClCompile Include="..\libusb\os\events_windows.c" />
     <ClCompile Include="..\libusb\hotplug.c" />
     <ClCompile Include="..\libusb\io.c" />
-    <ClCompile Include="..\libusb\os\poll_windows.c" />
     <ClCompile Include="..\libusb\strerror.c" />
     <ClCompile Include="..\libusb\sync.c" />
     <ClCompile Include="..\libusb\os\threads_windows.c" />
@@ -83,10 +83,10 @@
   </ItemGroup>
   <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\poll_windows.h" />
     <ClInclude Include="..\libusb\os\threads_windows.h" />
     <ClInclude Include="..\libusb\version.h" />
     <ClInclude Include="..\libusb\version_nano.h" />
diff --git a/msvc/libusb_dll_2015.vcxproj.filters b/msvc/libusb_dll_2015.vcxproj.filters
index 2e6cc93..8da28e3 100644
--- a/msvc/libusb_dll_2015.vcxproj.filters
+++ b/msvc/libusb_dll_2015.vcxproj.filters
@@ -18,6 +18,9 @@
     <ClInclude Include=".\config.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="..\libusb\os\events_windows.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="..\libusb\hotplug.h">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -27,9 +30,6 @@
     <ClInclude Include="..\libusb\libusbi.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="..\libusb\os\poll_windows.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
     <ClInclude Include="..\libusb\os\threads_windows.h">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -61,15 +61,15 @@
     <ClCompile Include="..\libusb\descriptor.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\libusb\os\events_windows.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="..\libusb\hotplug.c">
       <Filter>Source Files</Filter>
     </ClCompile>
     <ClCompile Include="..\libusb\io.c">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\libusb\os\poll_windows.c">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\libusb\strerror.c">
       <Filter>Source Files</Filter>
     </ClCompile>
diff --git a/msvc/libusb_dll_2017.vcxproj b/msvc/libusb_dll_2017.vcxproj
index 20fa6c4..598159d 100644
--- a/msvc/libusb_dll_2017.vcxproj
+++ b/msvc/libusb_dll_2017.vcxproj
@@ -90,9 +90,9 @@
   <ItemGroup>
     <ClCompile Include="..\libusb\core.c" />
     <ClCompile Include="..\libusb\descriptor.c" />
+    <ClCompile Include="..\libusb\os\events_windows.c" />
     <ClCompile Include="..\libusb\hotplug.c" />
     <ClCompile Include="..\libusb\io.c" />
-    <ClCompile Include="..\libusb\os\poll_windows.c" />
     <ClCompile Include="..\libusb\strerror.c" />
     <ClCompile Include="..\libusb\sync.c" />
     <ClCompile Include="..\libusb\os\threads_windows.c" />
@@ -102,10 +102,10 @@
   </ItemGroup>
   <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\poll_windows.h" />
     <ClInclude Include="..\libusb\os\threads_windows.h" />
     <ClInclude Include="..\libusb\version.h" />
     <ClInclude Include="..\libusb\version_nano.h" />
diff --git a/msvc/libusb_dll_2017.vcxproj.filters b/msvc/libusb_dll_2017.vcxproj.filters
index 2e6cc93..8da28e3 100644
--- a/msvc/libusb_dll_2017.vcxproj.filters
+++ b/msvc/libusb_dll_2017.vcxproj.filters
@@ -18,6 +18,9 @@
     <ClInclude Include=".\config.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="..\libusb\os\events_windows.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="..\libusb\hotplug.h">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -27,9 +30,6 @@
     <ClInclude Include="..\libusb\libusbi.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="..\libusb\os\poll_windows.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
     <ClInclude Include="..\libusb\os\threads_windows.h">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -61,15 +61,15 @@
     <ClCompile Include="..\libusb\descriptor.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\libusb\os\events_windows.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="..\libusb\hotplug.c">
       <Filter>Source Files</Filter>
     </ClCompile>
     <ClCompile Include="..\libusb\io.c">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\libusb\os\poll_windows.c">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\libusb\strerror.c">
       <Filter>Source Files</Filter>
     </ClCompile>
diff --git a/msvc/libusb_dll_2019.vcxproj b/msvc/libusb_dll_2019.vcxproj
index 69e42e1..dbd8717 100644
--- a/msvc/libusb_dll_2019.vcxproj
+++ b/msvc/libusb_dll_2019.vcxproj
@@ -90,9 +90,9 @@
   <ItemGroup>
     <ClCompile Include="..\libusb\core.c" />
     <ClCompile Include="..\libusb\descriptor.c" />
+    <ClCompile Include="..\libusb\os\events_windows.c" />
     <ClCompile Include="..\libusb\hotplug.c" />
     <ClCompile Include="..\libusb\io.c" />
-    <ClCompile Include="..\libusb\os\poll_windows.c" />
     <ClCompile Include="..\libusb\strerror.c" />
     <ClCompile Include="..\libusb\sync.c" />
     <ClCompile Include="..\libusb\os\threads_windows.c" />
@@ -102,10 +102,10 @@
   </ItemGroup>
   <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\poll_windows.h" />
     <ClInclude Include="..\libusb\os\threads_windows.h" />
     <ClInclude Include="..\libusb\version.h" />
     <ClInclude Include="..\libusb\version_nano.h" />
diff --git a/msvc/libusb_dll_2019.vcxproj.filters b/msvc/libusb_dll_2019.vcxproj.filters
index 2e6cc93..8da28e3 100644
--- a/msvc/libusb_dll_2019.vcxproj.filters
+++ b/msvc/libusb_dll_2019.vcxproj.filters
@@ -18,6 +18,9 @@
     <ClInclude Include=".\config.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="..\libusb\os\events_windows.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="..\libusb\hotplug.h">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -27,9 +30,6 @@
     <ClInclude Include="..\libusb\libusbi.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="..\libusb\os\poll_windows.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
     <ClInclude Include="..\libusb\os\threads_windows.h">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -61,15 +61,15 @@
     <ClCompile Include="..\libusb\descriptor.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\libusb\os\events_windows.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="..\libusb\hotplug.c">
       <Filter>Source Files</Filter>
     </ClCompile>
     <ClCompile Include="..\libusb\io.c">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\libusb\os\poll_windows.c">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\libusb\strerror.c">
       <Filter>Source Files</Filter>
     </ClCompile>
diff --git a/msvc/libusb_static_2013.vcxproj b/msvc/libusb_static_2013.vcxproj
index 559acf1..1b287e5 100644
--- a/msvc/libusb_static_2013.vcxproj
+++ b/msvc/libusb_static_2013.vcxproj
@@ -66,9 +66,9 @@
   <ItemGroup>
     <ClCompile Include="..\libusb\core.c" />
     <ClCompile Include="..\libusb\descriptor.c" />
+    <ClCompile Include="..\libusb\os\events_windows.c" />
     <ClCompile Include="..\libusb\hotplug.c" />
     <ClCompile Include="..\libusb\io.c" />
-    <ClCompile Include="..\libusb\os\poll_windows.c" />
     <ClCompile Include="..\libusb\strerror.c" />
     <ClCompile Include="..\libusb\sync.c" />
     <ClCompile Include="..\libusb\os\threads_windows.c" />
@@ -78,10 +78,10 @@
   </ItemGroup>
   <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\poll_windows.h" />
     <ClInclude Include="..\libusb\os\threads_windows.h" />
     <ClInclude Include="..\libusb\version.h" />
     <ClInclude Include="..\libusb\version_nano.h" />
diff --git a/msvc/libusb_static_2013.vcxproj.filters b/msvc/libusb_static_2013.vcxproj.filters
index c35bf87..2994ca1 100644
--- a/msvc/libusb_static_2013.vcxproj.filters
+++ b/msvc/libusb_static_2013.vcxproj.filters
@@ -14,6 +14,9 @@
     <ClInclude Include=".\config.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="..\libusb\os\events_windows.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="..\libusb\hotplug.h">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -23,9 +26,6 @@
     <ClInclude Include="..\libusb\libusbi.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="..\libusb\os\poll_windows.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
     <ClInclude Include="..\libusb\os\threads_windows.h">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -52,15 +52,15 @@
     <ClCompile Include="..\libusb\descriptor.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\libusb\os\events_windows.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="..\libusb\hotplug.c">
       <Filter>Source Files</Filter>
     </ClCompile>
     <ClCompile Include="..\libusb\io.c">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\libusb\os\poll_windows.c">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\libusb\strerror.c">
       <Filter>Source Files</Filter>
     </ClCompile>
diff --git a/msvc/libusb_static_2015.vcxproj b/msvc/libusb_static_2015.vcxproj
index ba07666..9fa30da 100644
--- a/msvc/libusb_static_2015.vcxproj
+++ b/msvc/libusb_static_2015.vcxproj
@@ -67,9 +67,9 @@
   <ItemGroup>
     <ClCompile Include="..\libusb\core.c" />
     <ClCompile Include="..\libusb\descriptor.c" />
+    <ClCompile Include="..\libusb\os\events_windows.c" />
     <ClCompile Include="..\libusb\hotplug.c" />
     <ClCompile Include="..\libusb\io.c" />
-    <ClCompile Include="..\libusb\os\poll_windows.c" />
     <ClCompile Include="..\libusb\strerror.c" />
     <ClCompile Include="..\libusb\sync.c" />
     <ClCompile Include="..\libusb\os\threads_windows.c" />
@@ -79,10 +79,10 @@
   </ItemGroup>
   <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\poll_windows.h" />
     <ClInclude Include="..\libusb\os\threads_windows.h" />
     <ClInclude Include="..\libusb\version.h" />
     <ClInclude Include="..\libusb\version_nano.h" />
diff --git a/msvc/libusb_static_2015.vcxproj.filters b/msvc/libusb_static_2015.vcxproj.filters
index c35bf87..2994ca1 100644
--- a/msvc/libusb_static_2015.vcxproj.filters
+++ b/msvc/libusb_static_2015.vcxproj.filters
@@ -14,6 +14,9 @@
     <ClInclude Include=".\config.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="..\libusb\os\events_windows.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="..\libusb\hotplug.h">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -23,9 +26,6 @@
     <ClInclude Include="..\libusb\libusbi.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="..\libusb\os\poll_windows.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
     <ClInclude Include="..\libusb\os\threads_windows.h">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -52,15 +52,15 @@
     <ClCompile Include="..\libusb\descriptor.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\libusb\os\events_windows.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="..\libusb\hotplug.c">
       <Filter>Source Files</Filter>
     </ClCompile>
     <ClCompile Include="..\libusb\io.c">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\libusb\os\poll_windows.c">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\libusb\strerror.c">
       <Filter>Source Files</Filter>
     </ClCompile>
diff --git a/msvc/libusb_static_2017.vcxproj b/msvc/libusb_static_2017.vcxproj
index 08816d2..62076e0 100644
--- a/msvc/libusb_static_2017.vcxproj
+++ b/msvc/libusb_static_2017.vcxproj
@@ -86,9 +86,9 @@
   <ItemGroup>
     <ClCompile Include="..\libusb\core.c" />
     <ClCompile Include="..\libusb\descriptor.c" />
+    <ClCompile Include="..\libusb\os\events_windows.c" />
     <ClCompile Include="..\libusb\hotplug.c" />
     <ClCompile Include="..\libusb\io.c" />
-    <ClCompile Include="..\libusb\os\poll_windows.c" />
     <ClCompile Include="..\libusb\strerror.c" />
     <ClCompile Include="..\libusb\sync.c" />
     <ClCompile Include="..\libusb\os\threads_windows.c" />
@@ -98,10 +98,10 @@
   </ItemGroup>
   <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\poll_windows.h" />
     <ClInclude Include="..\libusb\os\threads_windows.h" />
     <ClInclude Include="..\libusb\version.h" />
     <ClInclude Include="..\libusb\version_nano.h" />
diff --git a/msvc/libusb_static_2017.vcxproj.filters b/msvc/libusb_static_2017.vcxproj.filters
index c35bf87..2994ca1 100644
--- a/msvc/libusb_static_2017.vcxproj.filters
+++ b/msvc/libusb_static_2017.vcxproj.filters
@@ -14,6 +14,9 @@
     <ClInclude Include=".\config.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="..\libusb\os\events_windows.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="..\libusb\hotplug.h">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -23,9 +26,6 @@
     <ClInclude Include="..\libusb\libusbi.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="..\libusb\os\poll_windows.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
     <ClInclude Include="..\libusb\os\threads_windows.h">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -52,15 +52,15 @@
     <ClCompile Include="..\libusb\descriptor.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\libusb\os\events_windows.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="..\libusb\hotplug.c">
       <Filter>Source Files</Filter>
     </ClCompile>
     <ClCompile Include="..\libusb\io.c">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\libusb\os\poll_windows.c">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\libusb\strerror.c">
       <Filter>Source Files</Filter>
     </ClCompile>
diff --git a/msvc/libusb_static_2019.vcxproj b/msvc/libusb_static_2019.vcxproj
index 24ab3e3..60ad642 100644
--- a/msvc/libusb_static_2019.vcxproj
+++ b/msvc/libusb_static_2019.vcxproj
@@ -86,9 +86,9 @@
   <ItemGroup>
     <ClCompile Include="..\libusb\core.c" />
     <ClCompile Include="..\libusb\descriptor.c" />
+    <ClCompile Include="..\libusb\os\events_windows.c" />
     <ClCompile Include="..\libusb\hotplug.c" />
     <ClCompile Include="..\libusb\io.c" />
-    <ClCompile Include="..\libusb\os\poll_windows.c" />
     <ClCompile Include="..\libusb\strerror.c" />
     <ClCompile Include="..\libusb\sync.c" />
     <ClCompile Include="..\libusb\os\threads_windows.c" />
@@ -98,10 +98,10 @@
   </ItemGroup>
   <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\poll_windows.h" />
     <ClInclude Include="..\libusb\os\threads_windows.h" />
     <ClInclude Include="..\libusb\version.h" />
     <ClInclude Include="..\libusb\version_nano.h" />
diff --git a/msvc/libusb_static_2019.vcxproj.filters b/msvc/libusb_static_2019.vcxproj.filters
index c35bf87..2994ca1 100644
--- a/msvc/libusb_static_2019.vcxproj.filters
+++ b/msvc/libusb_static_2019.vcxproj.filters
@@ -14,6 +14,9 @@
     <ClInclude Include=".\config.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="..\libusb\os\events_windows.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="..\libusb\hotplug.h">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -23,9 +26,6 @@
     <ClInclude Include="..\libusb\libusbi.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="..\libusb\os\poll_windows.h">
-      <Filter>Header Files</Filter>
-    </ClInclude>
     <ClInclude Include="..\libusb\os\threads_windows.h">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -52,15 +52,15 @@
     <ClCompile Include="..\libusb\descriptor.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\libusb\os\events_windows.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="..\libusb\hotplug.c">
       <Filter>Source Files</Filter>
     </ClCompile>
     <ClCompile Include="..\libusb\io.c">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="..\libusb\os\poll_windows.c">
-      <Filter>Source Files</Filter>
-    </ClCompile>
     <ClCompile Include="..\libusb\strerror.c">
       <Filter>Source Files</Filter>
     </ClCompile>