diff --git a/windows/netuio/netuio.inf b/windows/netuio/netuio.inf
index d166868..cac378d 100644
--- a/windows/netuio/netuio.inf
+++ b/windows/netuio/netuio.inf
@@ -63,6 +63,9 @@ AddReg=Device.HW.Registry
 [Device.HW.Registry]
 ; Ensure that only administrators can access our device object.
 HKR,,Security,,"D:P(A;;GA;;;SY)(A;;GA;;;BA)"
+HKR,Interrupt Management,,0x00000010
+HKR,Interrupt Management\MessageSignaledInterruptProperties,,0x00000010
+HKR,Interrupt Management\MessageSignaledInterruptProperties,MSISupported,0x00010001,1
 
 ;-------------- Service installation
 [netuio_Device.NT.Services]
diff --git a/windows/netuio/netuio_dev.c b/windows/netuio/netuio_dev.c
index b2deb10..d4662b6 100644
--- a/windows/netuio/netuio_dev.c
+++ b/windows/netuio/netuio_dev.c
@@ -135,6 +135,109 @@ netuio_create_device(PWDFDEVICE_INIT DeviceInit)
     return status;
 }
 
+static EVT_WDF_INTERRUPT_ISR netuio_intr_service;
+static EVT_WDF_INTERRUPT_WORKITEM netuio_intr_deliver;
+
+_Use_decl_annotations_
+static BOOLEAN
+netuio_intr_service(WDFINTERRUPT intr, ULONG message_id)
+{
+	WDFDEVICE device = WdfInterruptGetDevice(intr);
+	NETUIO_CONTEXT_DATA *ctx = netuio_get_context_data(device);
+	BOOLEAN enabled = ctx->intr_enabled;
+
+	UNREFERENCED_PARAMETER(message_id);
+	if (enabled)
+		WdfInterruptQueueWorkItemForIsr(intr);
+	return enabled;
+}
+
+_Use_decl_annotations_
+static void
+netuio_intr_deliver(WDFINTERRUPT intr, WDFOBJECT device)
+{
+	NETUIO_INTR_CTX *intr_ctx = netuio_intr_ctx_get(intr);
+	WDFREQUEST request;
+	NTSTATUS status;
+
+	UNREFERENCED_PARAMETER(device);
+	PAGED_CODE();
+	status = WdfIoQueueRetrieveNextRequest(intr_ctx->queue, &request);
+	if (NT_SUCCESS(status))
+		WdfRequestComplete(request, STATUS_SUCCESS);
+}
+
+static NTSTATUS
+netuio_intr_alloc(WDFDEVICE device, WDFCMRESLIST raw, WDFCMRESLIST translated)
+{
+	NETUIO_CONTEXT_DATA *ctx;
+	CM_PARTIAL_RESOURCE_DESCRIPTOR *desc;
+	WDFINTERRUPT* intrs;
+	WDF_OBJECT_ATTRIBUTES intr_attr;
+	WDF_INTERRUPT_CONFIG intr_config;
+	WDF_OBJECT_ATTRIBUTES queue_attr;
+	WDF_IO_QUEUE_CONFIG queue_config;
+	ULONG i, resource_n, intr_n = 0, intr_created = 0;
+	NTSTATUS status;
+
+	resource_n = WdfCmResourceListGetCount(translated);
+	for (i = 0; i < resource_n; i++) {
+		desc = WdfCmResourceListGetDescriptor(translated, i);
+		if (desc->Type == CmResourceTypeInterrupt)
+			intr_n++;
+	}
+	if (intr_n == 0)
+		return STATUS_SUCCESS;
+
+	intrs = ExAllocatePoolZero(PagedPool, sizeof(intrs[0]) * intr_n, 'rtni');
+	if (intrs == NULL)
+		return STATUS_INSUFFICIENT_RESOURCES;
+
+	WDF_OBJECT_ATTRIBUTES_INIT(&queue_attr);
+	WDF_IO_QUEUE_CONFIG_INIT(&queue_config, WdfIoQueueDispatchManual);
+	WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&intr_attr, NETUIO_INTR_CTX);
+	WDF_INTERRUPT_CONFIG_INIT(&intr_config, netuio_intr_service, NULL);
+	intr_config.EvtInterruptWorkItem = netuio_intr_deliver;
+	for (i = 0; i < resource_n; i++) {
+		NETUIO_INTR_CTX *intr_ctx;
+		WDFINTERRUPT intr;
+		WDFQUEUE queue;
+
+		desc = WdfCmResourceListGetDescriptor(translated, i);
+		if (desc->Type != CmResourceTypeInterrupt)
+			continue;
+
+		intr_config.InterruptRaw = WdfCmResourceListGetDescriptor(raw, i);
+		intr_config.InterruptTranslated = desc;
+		status = WdfInterruptCreate(device, &intr_config, &intr_attr, &intr);
+		if (!NT_SUCCESS(status))
+			goto error;
+
+		queue_attr.ParentObject = intr;
+		status = WdfIoQueueCreate(device, &queue_config, &queue_attr, &queue);
+		if (!NT_SUCCESS(status)) {
+			WdfObjectDelete(intr);
+			goto error;
+		}
+
+		intr_ctx = netuio_intr_ctx_get(intr);
+		intr_ctx->queue = queue;
+		intrs[intr_created] = intr;
+		intr_created++;
+	}
+
+	ctx = netuio_get_context_data(device);
+	ctx->intr = intrs;
+	ctx->intr_n = intr_n;
+	return STATUS_SUCCESS;
+
+error:
+	for (i = 0; i < intr_created; i++)
+		WdfObjectDelete(intrs[i]);
+	ExFreePool(intrs);
+	return status;
+}
+
 _Use_decl_annotations_
 NTSTATUS
 netuio_map_hw_resources(WDFDEVICE Device, WDFCMRESLIST Resources, WDFCMRESLIST ResourcesTranslated)
@@ -208,7 +311,7 @@ netuio_map_hw_resources(WDFDEVICE Device, WDFCMRESLIST Resources, WDFCMRESLIST R
 
             if (descriptor == NULL) {
                 status = STATUS_DEVICE_CONFIGURATION_ERROR;
-                goto end;
+                goto end_of_loop;
             }
         } while ((descriptor->Type != CmResourceTypeMemory) ||
                  !(descriptor->Flags & CM_RESOURCE_MEMORY_BAR));
@@ -237,8 +340,9 @@ netuio_map_hw_resources(WDFDEVICE Device, WDFCMRESLIST Resources, WDFCMRESLIST R
 
         ctx->dpdk_hw[bar_index].mem.size = ctx->bar[bar_index].size;
     } // for bar_index
+end_of_loop:
 
-    status = STATUS_SUCCESS;
+    status = netuio_intr_alloc(Device, Resources, ResourcesTranslated);
 
 end:
     if (status != STATUS_SUCCESS) {
@@ -267,6 +371,7 @@ netuio_free_hw_resources(WDFDEVICE Device)
             MmUnmapIoSpace(ctx->bar[bar_index].virt_addr, ctx->bar[bar_index].size);
         }
     }
+    /* Interrupt objects are released with the device. */
 
     RtlZeroMemory(ctx->dpdk_hw, sizeof(ctx->dpdk_hw));
     RtlZeroMemory(ctx->bar, sizeof(ctx->bar));
diff --git a/windows/netuio/netuio_dev.h b/windows/netuio/netuio_dev.h
index 6285114..1c8a6fa 100644
--- a/windows/netuio/netuio_dev.h
+++ b/windows/netuio/netuio_dev.h
@@ -24,6 +24,13 @@ struct dev_addr {
     USHORT  func_num;
 };
 
+/** Interrupt vector associated data. */
+typedef struct NETUIO_INTR_CTX {
+    WDFQUEUE queue; /**< Queue of requests for this interrupt. */
+} NETUIO_INTR_CTX;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(NETUIO_INTR_CTX, netuio_intr_ctx_get);
+
 /**
  * The device context performs the same job as a WDM device extension in the driver frameworks
  */
@@ -34,6 +41,9 @@ typedef struct _NETUIO_CONTEXT_DATA
     struct pci_bar          bar[PCI_MAX_BAR];       /**< device BARs */
     struct dev_addr         addr;                   /**< B:D:F details of device */
     struct mem_map_region   dpdk_hw[PCI_MAX_BAR];   /**< mapped region for the device's register space */
+    BOOLEAN                 intr_enabled;           /**< Whether interrupt delivery is enabled. */
+    WDFINTERRUPT            *intr;                  /**< Interrupt objects indexed by vector. */
+    size_t                  intr_n;                 /**< Number of interrupt objects. */
 } NETUIO_CONTEXT_DATA, *PNETUIO_CONTEXT_DATA;
 
 
diff --git a/windows/netuio/netuio_interface.h b/windows/netuio/netuio_interface.h
index 71f5395..b5f936f 100644
--- a/windows/netuio/netuio_interface.h
+++ b/windows/netuio/netuio_interface.h
@@ -50,6 +50,20 @@ DEFINE_GUID (GUID_DEVINTERFACE_netUIO, 0x08336f60,0x0679,0x4c6c,0x85,0xd2,0xae,0
  */
 #define IOCTL_NETUIO_PCI_CONFIG_IO        CTL_CODE(FILE_DEVICE_NETWORK, 52, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
 
+/**
+ * Control whether interrupts are enabled.
+ * Input: struct netuio_intr_control
+ * Output: N/A
+ */
+#define IOCTL_NETUIO_INTR_CONTROL CTL_CODE(FILE_DEVICE_NETWORK, 53, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+/**
+ * Wait until an interrupt occurs.
+ * Input: struct netuio_intr_query
+ * Output: N/A
+ */
+#define IOCTL_NETUIO_INTR_QUERY CTL_CODE(FILE_DEVICE_NETWORK, 54, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
 struct mem_region {
     UINT64           size;       /**< memory region size */
     LARGE_INTEGER    phys_addr;  /**< physical address of the memory region */
@@ -83,6 +97,14 @@ struct dpdk_pci_config_io
     } data; /**< Data to be written, in case of write operations */
 };
 
+struct netuio_intr_control {
+    UINT32 enable; /**< 1 to enable interrupts, 0 to disable */
+};
+
+struct netuio_intr_query {
+    UINT32 vector; /**< Vector of the interrupt to wait for. */
+};
+
 #pragma pack(pop)
 
 #endif // NETUIO_INTERFACE_H
diff --git a/windows/netuio/netuio_queue.c b/windows/netuio/netuio_queue.c
index 0583394..1f9eb9e 100644
--- a/windows/netuio/netuio_queue.c
+++ b/windows/netuio/netuio_queue.c
@@ -245,37 +245,49 @@ end:
     return;
 }
 
-/*
-Routine Description:
-    This event is invoked when the framework receives IRP_MJ_DEVICE_CONTROL request.
-
-Return Value:
-    None
- */
-_Use_decl_annotations_
-VOID
-netuio_evt_IO_device_control(WDFQUEUE Queue, WDFREQUEST Request,
-                             size_t OutputBufferLength, size_t InputBufferLength,
-                             ULONG IoControlCode)
+static void
+netuio_ioctl_intr_control(NETUIO_CONTEXT_DATA *ctx, WDFREQUEST request)
 {
-    UNREFERENCED_PARAMETER(OutputBufferLength);
-    UNREFERENCED_PARAMETER(InputBufferLength);
-
-    NTSTATUS status = STATUS_SUCCESS;
-    PVOID    input_buf = NULL, output_buf = NULL;
-    size_t   input_buf_size, output_buf_size;
-    size_t  bytes_returned = 0;
-
-    WDFDEVICE device = WdfIoQueueGetDevice(Queue);
+	struct netuio_intr_control *in;
+	NTSTATUS status;
+
+	status = WdfRequestRetrieveInputBuffer(request, sizeof(*in), &in, NULL);
+	if (!NT_SUCCESS(status))
+		goto exit;
+	ctx->intr_enabled = in->enable != 0;
+	status = STATUS_SUCCESS;
+exit:
+	WdfRequestComplete(request, status);
+}
 
-    PNETUIO_CONTEXT_DATA  ctx;
-    ctx = netuio_get_context_data(device);
+static void
+netuio_ioctl_intr_query(NETUIO_CONTEXT_DATA *ctx, WDFREQUEST request)
+{
+	struct netuio_intr_query *in;
+	NETUIO_INTR_CTX *intr_ctx;
+	NTSTATUS status;
+
+	status = WdfRequestRetrieveInputBuffer(request, sizeof(*in), &in, NULL);
+	if (!NT_SUCCESS(status))
+		goto error;
+	if (in->vector >= ctx->intr_n) {
+		status = STATUS_INVALID_PARAMETER;
+		goto error;
+	}
+	intr_ctx = netuio_intr_ctx_get(ctx->intr[in->vector]);
+	WdfRequestForwardToIoQueue(request, intr_ctx->queue);
+	return;
+error:
+	WdfRequestComplete(request, status);
+}
 
-    if (IoControlCode != IOCTL_NETUIO_PCI_CONFIG_IO)
-    {
-        status = STATUS_INVALID_DEVICE_REQUEST;
-        goto end;
-    }
+static void
+netuio_ioctl_pci_config_io(NETUIO_CONTEXT_DATA *ctx, WDFREQUEST Request)
+{
+    PVOID input_buf = NULL, output_buf = NULL;
+    size_t input_buf_size, output_buf_size;
+    size_t bytes_returned = 0;
+    NTSTATUS status;
 
     // First retrieve the input buffer and see if it matches our device
     status = WdfRequestRetrieveInputBuffer(Request, sizeof(struct dpdk_pci_config_io), &input_buf, &input_buf_size);
@@ -298,7 +310,6 @@ netuio_evt_IO_device_control(WDFQUEUE Queue, WDFREQUEST Request,
     if (!NT_SUCCESS(status)) {
         goto end;
     }
-    ASSERT(output_buf_size == OutputBufferLength);
 
     if (dpdk_pci_io_input->op == PCI_IO_READ) {
         *(UINT64 *)output_buf = 0;
@@ -329,6 +340,37 @@ netuio_evt_IO_device_control(WDFQUEUE Queue, WDFREQUEST Request,
 
 end:
     WdfRequestCompleteWithInformation(Request, status, bytes_returned);
+}
 
-    return;
+/*
+Routine Description:
+    This event is invoked when the framework receives IRP_MJ_DEVICE_CONTROL request.
+
+Return Value:
+    None
+ */
+_Use_decl_annotations_
+VOID
+netuio_evt_IO_device_control(WDFQUEUE Queue, WDFREQUEST Request,
+		size_t OutputBufferLength, size_t InputBufferLength,
+		ULONG IoControlCode)
+{
+	WDFDEVICE device = WdfIoQueueGetDevice(Queue);
+	PNETUIO_CONTEXT_DATA ctx = netuio_get_context_data(device);
+
+	UNREFERENCED_PARAMETER(OutputBufferLength);
+	UNREFERENCED_PARAMETER(InputBufferLength);
+	switch (IoControlCode) {
+	case IOCTL_NETUIO_PCI_CONFIG_IO:
+		netuio_ioctl_pci_config_io(ctx, Request);
+		break;
+	case IOCTL_NETUIO_INTR_CONTROL:
+		netuio_ioctl_intr_control(ctx, Request);
+		break;
+	case IOCTL_NETUIO_INTR_QUERY:
+		netuio_ioctl_intr_query(ctx, Request);
+		break;
+	default:
+		WdfRequestComplete(Request, STATUS_INVALID_DEVICE_REQUEST);
+	}
 }
