diff --git a/lib/librte_eal/include/rte_eal_interrupts.h b/lib/librte_eal/include/rte_eal_interrupts.h
index b1e8a2934..b80edfc65 100644
--- a/lib/librte_eal/include/rte_eal_interrupts.h
+++ b/lib/librte_eal/include/rte_eal_interrupts.h
@@ -69,10 +69,18 @@ struct rte_epoll_event {
 struct rte_intr_handle {
 	RTE_STD_C11
 	union {
-		int vfio_dev_fd;  /**< VFIO device file descriptor */
-		int uio_cfg_fd;  /**< UIO cfg file desc for uio_pci_generic */
+		struct {
+			RTE_STD_C11
+			union {
+				/** VFIO device file descriptor */
+				int vfio_dev_fd;
+				/** UIO cfg file desc for uio_pci_generic */
+				int uio_cfg_fd;
+			};
+			int fd;	/**< interrupt event file descriptor */
+		};
+		void *handle; /**< device driver handle (Windows) */
 	};
-	int fd;	 /**< interrupt event file descriptor */
 	enum rte_intr_handle_type type;  /**< handle type */
 	uint32_t max_intr;             /**< max interrupt requested */
 	uint32_t nb_efd;               /**< number of available efd(event fd) */
diff --git a/lib/librte_eal/rte_eal_exports.def b/lib/librte_eal/rte_eal_exports.def
index cda3e0c79..dd8adfa23 100644
--- a/lib/librte_eal/rte_eal_exports.def
+++ b/lib/librte_eal/rte_eal_exports.def
@@ -70,6 +70,7 @@ EXPORTS
 	rte_vlog
 	rte_realloc
 	rte_strscpy
+	rte_thread_is_intr
 	rte_zmalloc
 	rte_zmalloc_socket
 
diff --git a/lib/librte_eal/windows/eal.c b/lib/librte_eal/windows/eal.c
index bc48f27ab..141f22adb 100644
--- a/lib/librte_eal/windows/eal.c
+++ b/lib/librte_eal/windows/eal.c
@@ -344,6 +344,11 @@ rte_eal_init(int argc, char **argv)
 		return -1;
 	}
 
+	if (rte_eal_intr_init() < 0) {
+		rte_eal_init_alert("Cannot init interrupt-handling thread");
+		return -1;
+	}
+
 	if (rte_eal_timer_init() < 0) {
 		rte_eal_init_alert("Cannot init TSC timer");
 		rte_errno = EFAULT;
diff --git a/lib/librte_eal/windows/eal_interrupts.c b/lib/librte_eal/windows/eal_interrupts.c
index d3ecdaccb..6c64a48f3 100644
--- a/lib/librte_eal/windows/eal_interrupts.c
+++ b/lib/librte_eal/windows/eal_interrupts.c
@@ -4,6 +4,81 @@
 
 #include <rte_interrupts.h>
 
+#include "eal_private.h"
+#include "eal_windows.h"
+
+static pthread_t intr_thread;
+
+static HANDLE intr_iocp;
+
+static void
+eal_intr_process(const OVERLAPPED_ENTRY *event)
+{
+	RTE_SET_USED(event);
+}
+
+static void *
+eal_intr_thread_main(LPVOID arg __rte_unused)
+{
+	while (1) {
+		OVERLAPPED_ENTRY events[16];
+		ULONG event_count, i;
+		BOOL result;
+
+		result = GetQueuedCompletionStatusEx(
+			intr_iocp, events, RTE_DIM(events), &event_count,
+			INFINITE, /* no timeout */
+			TRUE);    /* alertable wait for alarm APCs */
+
+		if (!result) {
+			DWORD error = GetLastError();
+			if (error != WAIT_IO_COMPLETION) {
+				RTE_LOG_WIN32_ERR("GetQueuedCompletionStatusEx()");
+				RTE_LOG(ERR, EAL, "Failed waiting for interrupts\n");
+				break;
+			}
+
+			/* No I/O events, all work is done in completed APCs. */
+			continue;
+		}
+
+		for (i = 0; i < event_count; i++)
+			eal_intr_process(&events[i]);
+	}
+
+	CloseHandle(intr_iocp);
+	intr_iocp = NULL;
+	return NULL;
+}
+
+int
+rte_eal_intr_init(void)
+{
+	int ret = 0;
+
+	intr_iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1);
+	if (intr_iocp == NULL) {
+		RTE_LOG_WIN32_ERR("CreateIoCompletionPort()");
+		RTE_LOG(ERR, EAL, "Cannot create interrupt IOCP\n");
+		return -1;
+	}
+
+	ret = rte_ctrl_thread_create(&intr_thread, "eal-intr-thread", NULL,
+			eal_intr_thread_main, NULL);
+	if (ret != 0) {
+		rte_errno = -ret;
+		RTE_LOG(ERR, EAL, "Cannot create interrupt thread\n");
+	}
+
+	return ret;
+}
+
+int
+rte_thread_is_intr(void)
+{
+	return pthread_equal(intr_thread, pthread_self());
+}
+
 int
 rte_intr_rx_ctl(__rte_unused struct rte_intr_handle *intr_handle,
 		__rte_unused int epfd, __rte_unused int op,
@@ -11,3 +86,22 @@ rte_intr_rx_ctl(__rte_unused struct rte_intr_handle *intr_handle,
 {
 	return -ENOTSUP;
 }
+
+int
+eal_intr_thread_schedule(void (*func)(void *arg), void *arg)
+{
+	HANDLE handle;
+
+	handle = OpenThread(THREAD_ALL_ACCESS, FALSE, intr_thread);
+	if (handle == NULL) {
+		RTE_LOG_WIN32_ERR("OpenThread(%llu)", intr_thread);
+		return -ENOENT;
+	}
+
+	if (!QueueUserAPC((PAPCFUNC)(ULONG_PTR)func, handle, (ULONG_PTR)arg)) {
+		RTE_LOG_WIN32_ERR("QueueUserAPC()");
+		return -EINVAL;
+	}
+
+	return 0;
+}
diff --git a/lib/librte_eal/windows/eal_windows.h b/lib/librte_eal/windows/eal_windows.h
index d48ee0a12..478accc1b 100644
--- a/lib/librte_eal/windows/eal_windows.h
+++ b/lib/librte_eal/windows/eal_windows.h
@@ -55,6 +55,18 @@ int eal_thread_create(pthread_t *thread);
  */
 unsigned int eal_socket_numa_node(unsigned int socket_id);
 
+/**
+ * Schedule code for execution in the interrupt thread.
+ *
+ * @param func
+ *  Function to call.
+ * @param arg
+ *  Argument to the called function.
+ * @return
+ *  0 on success, netagive error code on failure.
+ */
+int eal_intr_thread_schedule(void (*func)(void *arg), void *arg);
+
 /**
  * Open virt2phys driver interface device.
  *
diff --git a/lib/librte_eal/windows/include/pthread.h b/lib/librte_eal/windows/include/pthread.h
index 99013dc94..a4ab4d094 100644
--- a/lib/librte_eal/windows/include/pthread.h
+++ b/lib/librte_eal/windows/include/pthread.h
@@ -42,6 +42,13 @@ typedef SYNCHRONIZATION_BARRIER pthread_barrier_t;
 #define pthread_self() \
 	((pthread_t)GetCurrentThreadId())
 
+
+static inline int
+pthread_equal(pthread_t t1, pthread_t t2)
+{
+	return t1 == t2;
+}
+
 static inline int
 pthread_setaffinity_np(pthread_t threadid, size_t cpuset_size,
 			rte_cpuset_t *cpuset)
