DPDK patches and discussions
 help / color / mirror / Atom feed
* [dpdk-dev] [PATCH v2] windows/netuio: add Windows NetUIO kernel driver
@ 2020-08-20 22:23 Narcisa Ana Maria Vasile
  2020-08-21  1:32 ` Ranjit Menon
                   ` (2 more replies)
  0 siblings, 3 replies; 16+ messages in thread
From: Narcisa Ana Maria Vasile @ 2020-08-20 22:23 UTC (permalink / raw)
  To: dev, thomas, ocardona, haramakr, pallavi.kadam, ranjit.menon
  Cc: dmitry.kozliuk, dmitrym, Narcisa Vasile, Harini Ramakrishnan

From: Narcisa Vasile <navasile@microsoft.com>

The Windows NetUIO kernel driver allows the DPDK userspace
application to directly access the hardware.

Cc: Harini Ramakrishnan <Harini.Ramakrishnan@microsoft.com>
Cc: Omar Cardona <ocardona@microsoft.com>
Signed-off-by: Narcisa Vasile <navasile@microsoft.com>
---

v2:
  Fix license message in each file
  Minor fixes in README
  Remove RC file as it is autogenerated
  Remove HW Ids from Inf
  Resubmit as single patch

 .gitattributes                                |   4 +
 .gitignore                                    |   2 +
 windows/netuio/kernel/README_NetUIO.rst       |  64 +++
 .../netuio/kernel/windows/netuio/netuio.inf   |  78 ++++
 .../netuio/kernel/windows/netuio/netuio_dev.c | 388 +++++++++++++++++
 .../netuio/kernel/windows/netuio/netuio_dev.h |  61 +++
 .../netuio/kernel/windows/netuio/netuio_drv.c | 146 +++++++
 .../netuio/kernel/windows/netuio/netuio_drv.h |  32 ++
 .../kernel/windows/netuio/netuio_interface.h  |  73 ++++
 .../kernel/windows/netuio/netuio_queue.c      | 397 ++++++++++++++++++
 .../kernel/windows/netuio/netuio_queue.h      |  31 ++
 .../netuio/kernel/windows/netuio/resource.h   |  14 +
 .../mk/exec-env/windows/netuio/netuio.sln     |  24 ++
 .../mk/exec-env/windows/netuio/netuio.vcxproj | 113 +++++
 .../windows/netuio/netuio.vcxproj.filters     |  54 +++
 .../windows/netuio/netuio.vcxproj.user        |  11 +
 16 files changed, 1492 insertions(+)
 create mode 100644 .gitattributes
 create mode 100644 .gitignore
 create mode 100644 windows/netuio/kernel/README_NetUIO.rst
 create mode 100644 windows/netuio/kernel/windows/netuio/netuio.inf
 create mode 100644 windows/netuio/kernel/windows/netuio/netuio_dev.c
 create mode 100644 windows/netuio/kernel/windows/netuio/netuio_dev.h
 create mode 100644 windows/netuio/kernel/windows/netuio/netuio_drv.c
 create mode 100644 windows/netuio/kernel/windows/netuio/netuio_drv.h
 create mode 100644 windows/netuio/kernel/windows/netuio/netuio_interface.h
 create mode 100644 windows/netuio/kernel/windows/netuio/netuio_queue.c
 create mode 100644 windows/netuio/kernel/windows/netuio/netuio_queue.h
 create mode 100644 windows/netuio/kernel/windows/netuio/resource.h
 create mode 100644 windows/netuio/mk/exec-env/windows/netuio/netuio.sln
 create mode 100644 windows/netuio/mk/exec-env/windows/netuio/netuio.vcxproj
 create mode 100644 windows/netuio/mk/exec-env/windows/netuio/netuio.vcxproj.filters
 create mode 100644 windows/netuio/mk/exec-env/windows/netuio/netuio.vcxproj.user

diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 000000000..13482db3d
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,4 @@
+* text=auto
+
+*.sln text eol=crlf
+*.vcxproj text eol=crlf
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..543281e8f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+x64/
+.vs/
diff --git a/windows/netuio/kernel/README_NetUIO.rst b/windows/netuio/kernel/README_NetUIO.rst
new file mode 100644
index 000000000..a290fcf20
--- /dev/null
+++ b/windows/netuio/kernel/README_NetUIO.rst
@@ -0,0 +1,64 @@
+..  SPDX-License-Identifier: BSD-3-Clause
+    Copyright(c) 2020 Microsoft Corporation.
+
+Compiling the NetUIO Driver from Source
+=======================================
+
+Operating System
+~~~~~~~~~~~~~~~~
+
+The NetUIO source has been validated against the following operating systems:
+
+* Windows Server 2016
+* Windows Server 2019
+
+Hardware Requirements
+~~~~~~~~~~~~~~~~~~~~~
+The NetUIO driver has been validated using the following network adapters on the Windows platform:
+
+*
+*
+
+Software Requirements
+~~~~~~~~~~~~~~~~~~~~~
+
+* Install Microsoft Visual Studio 2017 or Visual Stuido Studio 2019 Enterprise from https://visualstudio.microsoft.com/downloads/ 
+    * During installation ensure following components are selected:
+        Windows 10 SDK (10.0.15063.0)
+        Windows 10 SDK (10.0.17763.0)
+        Windows Drivers Kit
+
+* Install WDK for Windows 10 (10.0.17763.1) from https://docs.microsoft.com/en-us/windows-hardware/drivers/download-the-wdk 
+
+Building the NetUIO Driver
+--------------------------
+Follow the steps below to build the NetUIO driver and install the driver for the network adapter. 
+
+* Clone the dpdk-kmods repository: git clone git://dpdk.org/dpdk-kmods
+* Navigate to \dpdk-kmods\windows\netuio\mk\exec-env\windows\netuio
+* Load netuio.sln in Microsoft Visual Studio 2017 or 2019
+* Choose Release as the configuration mode and Build the solution
+* The resultant output files can be found in \dpdk-kmods\windows\netuio\x64\Release\netuio
+ 
+Installing netuio.sys Driver for development
+--------------------------------------------
+.. note::
+
+In a development environment, NetUIO driver can be loaded by enabling test-signing.
+Please implement adequate precautionary measures before installing a test-signed driver, replacing a signed-driver.
+
+To ensure test-signed kernel-mode drivers can load on Windows, enable test-signing, using the following BCDEdit command.
+
+C:\windows\system32>Bcdedit.exe -set TESTSIGNING ON
+
+Windows displays the text “Test Mode” to remind users the system has test-signing enabled. 
+Refer to the MSDN documentation on how to Test-Sign a Driver Package.
+ 
+To procure a WHQL signed NetUIO driver for Windows, please reach out to dpdkwin@microsoft.com
+
+* Go to Device Manager -> Network Adapters.
+* Right Click on target network adapter -> Select Update Driver.
+* Select "Browse my computer for driver software".
+* In the resultant window, select "Let me pick from a list of available drivers on my computer".
+* Select "DPDK netUIO for Network Adapter" from the list of drivers.
+* The NetUIO.sys driver is successfully installed.
diff --git a/windows/netuio/kernel/windows/netuio/netuio.inf b/windows/netuio/kernel/windows/netuio/netuio.inf
new file mode 100644
index 000000000..88e90b365
--- /dev/null
+++ b/windows/netuio/kernel/windows/netuio/netuio.inf
@@ -0,0 +1,78 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright (c) Microsoft Corporation. All rights reserved
+;
+; netuio.inf
+;
+
+[Version]
+Signature="$WINDOWS NT$"
+Class=%ClassName%
+ClassGuid={78912BC1-CB8E-4B28-A329-F322EBADBE0F}
+Provider=%Intel%
+CatalogFile=netuio.cat
+DriverVer=
+
+[ClassInstall32]
+Addreg=netuioClassReg
+
+[netuioClassReg]
+HKR,,,0,%ClassName%
+HKR,,Icon,,-5
+
+;*****************************************
+; Install Section
+;*****************************************
+
+[Manufacturer]
+
+[netuio_Device.NT]
+CopyFiles=Drivers_Dir
+
+[Drivers_Dir]
+netuio.sys
+
+[netuio_Device.NT.HW]
+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)"
+
+;-------------- Service installation
+[netuio_Device.NT.Services]
+AddService = netuio,%SPSVCINST_ASSOCSERVICE%, netuio_Service_Inst
+
+; -------------- netuio driver install sections
+[netuio_Service_Inst]
+DisplayName    = %netuio.SVCDESC%
+ServiceType    = 1               ; SERVICE_KERNEL_DRIVER
+StartType      = 3               ; SERVICE_DEMAND_START
+ErrorControl   = 1               ; SERVICE_ERROR_NORMAL
+ServiceBinary  = %12%\netuio.sys
+AddReg         = DMAr.reg
+
+[DestinationDirs]
+DefaultDestDir = 12
+
+[SourceDisksNames]
+1 = %DiskName%,,,""
+
+[SourceDisksFiles]
+netuio.sys  = 1,,
+
+[netuio_Device.NT.Wdf]
+KmdfService =  netuio, netuio_wdfsect
+[netuio_wdfsect]
+KmdfLibraryVersion = $KMDFVERSION$
+
+[Strings]
+SPSVCINST_ASSOCSERVICE= 0x00000002
+Intel = "Intel"
+Broadcom = "Broadcom Corporation"
+ClassName = "Windows UIO"
+DiskName = "DPDK netUIO Installation Disk"
+netuio.DeviceDesc = "netuio Device"
+netuio.SVCDESC = "netuio Service"
+
+[DMAr.reg]
+HKR,Parameters,DmaRemappingCompatible,0x00010001,1
diff --git a/windows/netuio/kernel/windows/netuio/netuio_dev.c b/windows/netuio/kernel/windows/netuio/netuio_dev.c
new file mode 100644
index 000000000..6394bb5d1
--- /dev/null
+++ b/windows/netuio/kernel/windows/netuio/netuio_dev.c
@@ -0,0 +1,388 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) Microsoft Corporation. All rights reserved
+ */
+
+#include <stdio.h>
+#include "netuio_drv.h"
+
+#include <wdmguid.h>
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text (PAGE, netuio_create_device)
+#pragma alloc_text (PAGE, netuio_evt_device_context_cleanup)
+#pragma alloc_text (PAGE, netuio_map_hw_resources)
+#pragma alloc_text (PAGE, netuio_free_hw_resources)
+#endif
+
+/*
+Routine Description:
+    Worker routine called to create a device and its software resources.
+
+Return Value:
+    NTSTATUS
+ */
+NTSTATUS
+netuio_create_device(_Inout_ PWDFDEVICE_INIT DeviceInit)
+{
+    NTSTATUS status;
+    WDFDEVICE device = NULL;
+
+    WDF_OBJECT_ATTRIBUTES deviceAttributes;
+    WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;
+    WDF_FILEOBJECT_CONFIG fileConfig;
+
+    PAGED_CODE();
+
+    // Register PnP power management callbacks
+    WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
+    pnpPowerCallbacks.EvtDevicePrepareHardware = netuio_evt_prepare_hw;
+    pnpPowerCallbacks.EvtDeviceReleaseHardware = netuio_evt_release_hw;
+    WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);
+
+    // Register callbacks for when a HANDLE is opened or closed.
+    WDF_FILEOBJECT_CONFIG_INIT(&fileConfig, WDF_NO_EVENT_CALLBACK, WDF_NO_EVENT_CALLBACK, netuio_evt_file_cleanup);
+    WdfDeviceInitSetFileObjectConfig(DeviceInit, &fileConfig, WDF_NO_OBJECT_ATTRIBUTES);
+
+    // Set the device context cleanup callback.
+    // This function will be called when the WDF Device Object associated to the current device is destroyed
+    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, NETUIO_CONTEXT_DATA);
+    deviceAttributes.EvtCleanupCallback = netuio_evt_device_context_cleanup;
+    WdfDeviceInitSetIoInCallerContextCallback(DeviceInit, netuio_evt_IO_in_caller_context);
+
+    status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);
+
+	if (NT_SUCCESS(status)) {
+		// Create a device interface so that applications can find and talk to us.
+		status = WdfDeviceCreateDeviceInterface(device, &GUID_DEVINTERFACE_netUIO, NULL);
+	}
+
+    if (NT_SUCCESS(status)) {
+        // Retrieve and store PCI information
+        status = get_pci_device_info(device);
+    }
+
+    if (NT_SUCCESS(status)) {
+        // Create a symbolic link name for user-space access
+        status = create_device_specific_symbolic_link(device);
+    }
+
+    if (NT_SUCCESS(status)) {
+        // Initialize the I/O Package and any Queues
+        status = netuio_queue_initialize(device);
+    }
+
+    if (NT_SUCCESS(status)) {
+        // Allocate physically contiguous memory for user process use. We'll map it later
+        status = allocate_usermemory_segment(device);
+    }
+
+    return status;
+}
+
+/*
+Routine Description:
+    Free all the resources allocated in AdfEvtDeviceAdd.
+
+Return Value:
+    None
+ */
+VOID
+netuio_evt_device_context_cleanup(_In_ WDFOBJECT Device)
+{
+    free_usermemory_segment(Device);
+    return;
+}
+
+NTSTATUS
+netuio_map_hw_resources(_In_ WDFDEVICE Device, _In_ WDFCMRESLIST Resources, _In_ WDFCMRESLIST ResourcesTranslated)
+{
+    UNREFERENCED_PARAMETER(Resources);
+
+    NTSTATUS status;
+
+    PNETUIO_CONTEXT_DATA  netuio_contextdata;
+    netuio_contextdata = netuio_get_context_data(Device);
+
+    if (!netuio_contextdata) {
+        return STATUS_UNSUCCESSFUL;
+    }
+
+    PCI_COMMON_HEADER pci_config = {0};
+    ULONG bytes_returned;
+
+    // Read PCI configuration space
+    bytes_returned = netuio_contextdata->bus_interface.GetBusData(
+        netuio_contextdata->bus_interface.Context,
+        PCI_WHICHSPACE_CONFIG,
+        &pci_config,
+        0,
+        sizeof(pci_config));
+
+    if (bytes_returned != sizeof(pci_config)) {
+        status = STATUS_NOT_SUPPORTED;
+        goto end;
+    }
+
+    // Device type is implictly enforced by .inf
+    ASSERT(PCI_CONFIGURATION_TYPE(&pci_config) == PCI_DEVICE_TYPE);
+
+    PCM_PARTIAL_RESOURCE_DESCRIPTOR descriptor;
+    ULONG next_descriptor = 0;
+    ULONGLONG bar_addr = 0;
+    ULONG curr_bar = 0;
+    ULONG prev_bar = 0;
+
+    for (INT bar_index = 0; bar_index < PCI_MAX_BAR; bar_index++) {
+        prev_bar = curr_bar;
+        curr_bar = pci_config.u.type0.BaseAddresses[bar_index];
+        if (curr_bar == 0 || (prev_bar & PCI_TYPE_64BIT)) {
+            // Skip this bar
+            netuio_contextdata->bar[bar_index].base_addr.QuadPart = 0;
+            netuio_contextdata->bar[bar_index].size = 0;
+            netuio_contextdata->bar[bar_index].virt_addr = 0;
+
+            netuio_contextdata->dpdk_hw[bar_index].mdl = NULL;
+            netuio_contextdata->dpdk_hw[bar_index].mem.size = 0;
+
+            continue;
+        }
+
+        // Find next CmResourceTypeMemory
+        do {
+            descriptor = WdfCmResourceListGetDescriptor(ResourcesTranslated, next_descriptor);
+            next_descriptor++;
+
+            if (descriptor == NULL) {
+                status = STATUS_DEVICE_CONFIGURATION_ERROR;
+                goto end;
+            }
+        } while (descriptor->Type != CmResourceTypeMemory);
+
+        // Assert that we have the correct descriptor
+        ASSERT((descriptor->Flags & CM_RESOURCE_MEMORY_BAR) != 0);
+
+        if (curr_bar & PCI_TYPE_64BIT) {
+            ASSERT(bar_index != PCI_TYPE0_ADDRESSES - 1);
+            bar_addr = ((ULONGLONG)pci_config.u.type0.BaseAddresses[bar_index + 1] << 32) | (curr_bar & PCI_ADDRESS_MEMORY_ADDRESS_MASK);
+        }
+        else
+        {
+            bar_addr = curr_bar & PCI_ADDRESS_MEMORY_ADDRESS_MASK;
+        }
+
+        ASSERT((ULONGLONG)descriptor->u.Memory.Start.QuadPart == bar_addr);
+
+        // Retrieve and map the BARs
+        netuio_contextdata->bar[bar_index].base_addr.QuadPart = descriptor->u.Memory.Start.QuadPart;
+        netuio_contextdata->bar[bar_index].size = descriptor->u.Memory.Length;
+        netuio_contextdata->bar[bar_index].virt_addr = MmMapIoSpace(descriptor->u.Memory.Start,
+                                                                    descriptor->u.Memory.Length,
+                                                                    MmNonCached);
+        if (netuio_contextdata->bar[bar_index].virt_addr == NULL) {
+            status = STATUS_INSUFFICIENT_RESOURCES;
+            goto end;
+        }
+
+        // Allocate an MDL for the device BAR, so we can map it to the user's process context later.
+        netuio_contextdata->dpdk_hw[bar_index].mdl = IoAllocateMdl(netuio_contextdata->bar[bar_index].virt_addr,
+                                                                   (ULONG)netuio_contextdata->bar[bar_index].size,
+                                                                   FALSE,
+                                                                   FALSE,
+                                                                   NULL);
+        if (!netuio_contextdata->dpdk_hw[bar_index].mdl) {
+            status = STATUS_INSUFFICIENT_RESOURCES;
+            goto end;
+        }
+
+        netuio_contextdata->dpdk_hw[bar_index].mem.size = netuio_contextdata->bar[bar_index].size;
+    } // for bar_index
+
+    status = STATUS_SUCCESS;
+
+end:
+    if (status != STATUS_SUCCESS) {
+        netuio_free_hw_resources(Device);
+    }
+
+    return status;
+}
+
+VOID
+netuio_free_hw_resources(_In_ WDFDEVICE Device)
+{
+    PNETUIO_CONTEXT_DATA  netuio_contextdata;
+    netuio_contextdata = netuio_get_context_data(Device);
+
+    if (netuio_contextdata) {
+        for (UINT8 bar_index = 0; bar_index < PCI_MAX_BAR; bar_index++) {
+
+            // Free the allocated MDLs
+            if (netuio_contextdata->dpdk_hw[bar_index].mdl) {
+                IoFreeMdl(netuio_contextdata->dpdk_hw[bar_index].mdl);
+            }
+
+            // Unmap all the BAR regions previously mapped
+            if (netuio_contextdata->bar[bar_index].virt_addr) {
+                MmUnmapIoSpace(netuio_contextdata->bar[bar_index].virt_addr, netuio_contextdata->bar[bar_index].size);
+            }
+        }
+
+        RtlZeroMemory(netuio_contextdata->dpdk_hw, sizeof(netuio_contextdata->dpdk_hw));
+        RtlZeroMemory(netuio_contextdata->bar, sizeof(netuio_contextdata->bar));
+    }
+}
+
+
+static NTSTATUS
+get_pci_device_info(_In_ WDFOBJECT device)
+{
+    NTSTATUS status = STATUS_UNSUCCESSFUL;
+
+    PNETUIO_CONTEXT_DATA  netuio_contextdata;
+    netuio_contextdata = netuio_get_context_data(device);
+
+    if (!netuio_contextdata)
+        return status;
+
+    netuio_contextdata->wdf_device = device;  // Store for later use
+
+    // Obtain the BUS_INTERFACE_STANDARD interface from the Bus Driver
+    status = WdfFdoQueryForInterface(device, &GUID_BUS_INTERFACE_STANDARD,
+                                    (PINTERFACE)&netuio_contextdata->bus_interface,
+                                    sizeof(BUS_INTERFACE_STANDARD), 1, NULL);
+    if (!NT_SUCCESS(status))
+        return status;
+
+    // Retrieve the B:D:F details of our device
+    PDEVICE_OBJECT pdo = NULL;
+    pdo = WdfDeviceWdmGetPhysicalDevice(device);
+    if (pdo) {
+        ULONG prop = 0, length = 0;
+        status = IoGetDeviceProperty(pdo, DevicePropertyBusNumber, sizeof(ULONG), (PVOID)&netuio_contextdata->addr.bus_num, &length);
+        status = IoGetDeviceProperty(pdo, DevicePropertyAddress, sizeof(ULONG), (PVOID)&prop, &length);
+
+        if (NT_SUCCESS(status)) {
+            netuio_contextdata->addr.func_num = prop & 0x0000FFFF;
+            netuio_contextdata->addr.dev_num = ((prop >> 16) & 0x0000FFFF);
+        }
+        // Also, retrieve the NUMA node of the device
+        USHORT numaNode;
+        status = IoGetDeviceNumaNode(pdo, &numaNode);
+        if (NT_SUCCESS(status)) {
+            netuio_contextdata->dev_numa_node = numaNode;
+        }
+    }
+
+    return status;
+}
+
+static NTSTATUS
+create_device_specific_symbolic_link(_In_ WDFOBJECT device)
+{
+    NTSTATUS status = STATUS_UNSUCCESSFUL;
+    UNICODE_STRING netuio_symbolic_link;
+
+    PNETUIO_CONTEXT_DATA  netuio_contextdata;
+    netuio_contextdata = netuio_get_context_data(device);
+
+    if (!netuio_contextdata)
+        return status;
+
+    // Build symbolic link name as <netuio_symbolic_link>_BDF  (bus/device/func)
+    CHAR  symbolic_link[64] = { 0 };
+    sprintf_s(symbolic_link, sizeof(symbolic_link), "%s_%04d%02d%02d",
+                            NETUIO_DEVICE_SYMBOLIC_LINK_ANSI, netuio_contextdata->addr.bus_num,
+                            netuio_contextdata->addr.dev_num, netuio_contextdata->addr.func_num);
+
+    ANSI_STRING ansi_symbolic_link;
+    RtlInitAnsiString(&ansi_symbolic_link, symbolic_link);
+
+    status = RtlAnsiStringToUnicodeString(&netuio_symbolic_link, &ansi_symbolic_link, TRUE);
+    if (!NT_SUCCESS(status))
+        return status;
+
+    status = WdfDeviceCreateSymbolicLink(device, &netuio_symbolic_link);
+
+    RtlFreeUnicodeString(&netuio_symbolic_link);
+
+    return status;
+}
+
+static NTSTATUS
+allocate_usermemory_segment(_In_ WDFOBJECT device)
+{
+    NTSTATUS status;
+
+    PNETUIO_CONTEXT_DATA  netuio_contextdata;
+    netuio_contextdata = netuio_get_context_data(device);
+
+    if (!netuio_contextdata)
+    {
+        status = STATUS_INVALID_DEVICE_STATE;
+        goto end;
+    }
+
+    PHYSICAL_ADDRESS lowest_acceptable_address;
+    PHYSICAL_ADDRESS highest_acceptable_address;
+    PHYSICAL_ADDRESS boundary_address_multiple;
+
+    lowest_acceptable_address.QuadPart = 0x0000000000000000;
+    highest_acceptable_address.QuadPart = 0xFFFFFFFFFFFFFFFF;
+    boundary_address_multiple.QuadPart = 0;
+
+    // Allocate physically contiguous memory for user process use
+    netuio_contextdata->dpdk_seg.mem.virt_addr =
+                MmAllocateContiguousMemorySpecifyCache(USER_MEMORY_SEGMENT_SIZE,
+                                                       lowest_acceptable_address,
+                                                       highest_acceptable_address,
+                                                       boundary_address_multiple,
+                                                       MmCached);
+
+    if (!netuio_contextdata->dpdk_seg.mem.virt_addr) {
+        status = STATUS_NO_MEMORY;
+        goto end;
+    }
+
+    netuio_contextdata->dpdk_seg.mem.size = USER_MEMORY_SEGMENT_SIZE;
+
+    // Allocate an MDL for this memory region - so that we can map it into the user's process context later
+    netuio_contextdata->dpdk_seg.mdl = IoAllocateMdl((PVOID)netuio_contextdata->dpdk_seg.mem.virt_addr, USER_MEMORY_SEGMENT_SIZE, FALSE, FALSE, NULL);
+    if (netuio_contextdata->dpdk_seg.mdl == NULL) {
+        status = STATUS_NO_MEMORY;
+        goto end;
+    }
+
+    // Store the region's physical address
+    netuio_contextdata->dpdk_seg.mem.phys_addr = MmGetPhysicalAddress(netuio_contextdata->dpdk_seg.mem.virt_addr);
+
+    status = STATUS_SUCCESS;
+
+end:
+    if (status != STATUS_SUCCESS)
+    {
+        free_usermemory_segment(device);
+    }
+
+    return status;
+}
+
+static VOID
+free_usermemory_segment(_In_ WDFOBJECT device)
+{
+    PNETUIO_CONTEXT_DATA  netuio_contextdata;
+    netuio_contextdata = netuio_get_context_data(device);
+
+    if (netuio_contextdata) {
+        if (netuio_contextdata->dpdk_seg.mdl)
+        {
+            IoFreeMdl(netuio_contextdata->dpdk_seg.mdl);
+            netuio_contextdata->dpdk_seg.mdl = NULL;
+        }
+
+        if (netuio_contextdata->dpdk_seg.mem.virt_addr)
+        {
+            MmFreeContiguousMemory(netuio_contextdata->dpdk_seg.mem.virt_addr);
+            netuio_contextdata->dpdk_seg.mem.virt_addr = NULL;
+        }
+    }
+}
diff --git a/windows/netuio/kernel/windows/netuio/netuio_dev.h b/windows/netuio/kernel/windows/netuio/netuio_dev.h
new file mode 100644
index 000000000..82071a220
--- /dev/null
+++ b/windows/netuio/kernel/windows/netuio/netuio_dev.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) Microsoft Corporation. All rights reserved
+ */
+
+#ifndef NETUIO_DEV_H
+#define NETUIO_DEV_H
+
+EXTERN_C_START
+
+#include "netuio_interface.h"
+
+// Constants
+#define USER_MEMORY_SEGMENT_SIZE (256ULL * 1024ULL * 1024ULL)   // 256MB
+
+struct pci_bar {
+    PHYSICAL_ADDRESS base_addr;
+    PVOID            virt_addr;
+    UINT64           size;
+};
+
+struct mem_map_region {
+    PMDL               mdl;    // MDL describing the memory region
+    struct mem_region  mem;    // Memory region details
+};
+
+// The device context performs the same job as a WDM device extension in the driver frameworks
+typedef struct _NETUIO_CONTEXT_DATA
+{
+    WDFDEVICE               wdf_device;             // WDF device handle to the FDO
+    BUS_INTERFACE_STANDARD  bus_interface;          // Bus interface for config space access
+    struct pci_bar          bar[PCI_MAX_BAR];       // device BARs
+    struct dev_addr         addr;                   // B:D:F details of device
+    USHORT                  dev_numa_node;          // The NUMA node of the device
+    struct mem_map_region   dpdk_hw[PCI_MAX_BAR];   // mapped region for the device's register space
+    struct mem_map_region   dpdk_seg;               // mapped region allocated for DPDK process use
+} NETUIO_CONTEXT_DATA, *PNETUIO_CONTEXT_DATA;
+
+
+// This macro will generate an inline function called DeviceGetContext
+// which will be used to get a pointer to the device context memory in a type safe manner.
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(NETUIO_CONTEXT_DATA, netuio_get_context_data)
+
+
+// Function to initialize the device and its callbacks
+NTSTATUS netuio_create_device(_Inout_ PWDFDEVICE_INIT DeviceInit);
+NTSTATUS netuio_map_hw_resources(_In_ WDFDEVICE Device, _In_ WDFCMRESLIST Resources, _In_ WDFCMRESLIST ResourcesTranslated);
+VOID netuio_free_hw_resources(_In_ WDFDEVICE Device);
+
+
+// Function called for cleanup when device object is being destroyed
+VOID netuio_evt_device_context_cleanup(_In_ WDFOBJECT Device);
+
+// Local function protoyypes
+static NTSTATUS get_pci_device_info(_In_ WDFOBJECT device);
+static NTSTATUS create_device_specific_symbolic_link(_In_ WDFOBJECT device);
+static NTSTATUS allocate_usermemory_segment(_In_ WDFOBJECT device);
+static VOID free_usermemory_segment(_In_ WDFOBJECT device);
+
+EXTERN_C_END
+
+#endif // NETUIO_DEV_H
diff --git a/windows/netuio/kernel/windows/netuio/netuio_drv.c b/windows/netuio/kernel/windows/netuio/netuio_drv.c
new file mode 100644
index 000000000..4b00b01f0
--- /dev/null
+++ b/windows/netuio/kernel/windows/netuio/netuio_drv.c
@@ -0,0 +1,146 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) Microsoft Corporation. All rights reserved
+ */
+
+#include "netuio_drv.h"
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text (INIT, DriverEntry)
+#pragma alloc_text (PAGE, netuio_evt_device_add)
+#pragma alloc_text (PAGE, netuio_evt_driver_context_cleanup)
+#endif
+
+
+/*
+Routine Description:
+    DriverEntry initializes the driver and is the first routine called by the
+    system after the driver is loaded. DriverEntry specifies the other entry
+    points in the function driver, such as EvtDevice and DriverUnload.
+
+Return Value:
+    STATUS_SUCCESS if successful,
+    STATUS_UNSUCCESSFUL otherwise.
+ */
+NTSTATUS
+DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath)
+{
+    WDF_DRIVER_CONFIG config;
+    NTSTATUS status;
+    WDF_OBJECT_ATTRIBUTES attributes;
+
+    // Register a cleanup callback so that we can call WPP_CLEANUP when
+    // the framework driver object is deleted during driver unload.
+    WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+    attributes.EvtCleanupCallback = netuio_evt_driver_context_cleanup;
+
+    WDF_DRIVER_CONFIG_INIT(&config, netuio_evt_device_add);
+
+    status = WdfDriverCreate(DriverObject, RegistryPath,
+                             &attributes, &config,
+                             WDF_NO_HANDLE);
+
+    if (!NT_SUCCESS(status)) {
+        return status;
+    }
+
+    return status;
+}
+
+
+/*
+Routine Description:
+    netuio_evt_device_add is called by the framework in response to AddDevice
+    call from the PnP manager. We create and initialize a device object to
+    represent a new instance of the device.
+
+Return Value:
+    NTSTATUS
+ */
+NTSTATUS
+netuio_evt_device_add(_In_ WDFDRIVER Driver, _Inout_ PWDFDEVICE_INIT DeviceInit)
+{
+    UNREFERENCED_PARAMETER(Driver);
+    return netuio_create_device(DeviceInit);
+}
+
+/*
+Routine Description :
+    Maps HW resources and retrieves the PCI BAR address(es) of the device
+
+Return Value :
+    STATUS_SUCCESS is successful.
+    STATUS_<ERROR> otherwise
+-*/
+NTSTATUS
+netuio_evt_prepare_hw(_In_ WDFDEVICE Device, _In_ WDFCMRESLIST Resources, _In_ WDFCMRESLIST ResourcesTranslated)
+{
+    NTSTATUS status;
+
+    status = netuio_map_hw_resources(Device, Resources, ResourcesTranslated);
+
+    if (NT_SUCCESS(status)) {
+        PNETUIO_CONTEXT_DATA  netuio_contextdata;
+        netuio_contextdata = netuio_get_context_data(Device);
+        if (netuio_contextdata) {
+            DbgPrintEx(DPFLTR_IHVNETWORK_ID, DPFLTR_NETUIO_INFO_LEVEL, "netUIO Driver loaded...on device (B:D:F) %04d:%02d:%02d\n",
+                                             netuio_contextdata->addr.bus_num, netuio_contextdata->addr.dev_num, netuio_contextdata->addr.func_num);
+        }
+    }
+    return status;
+}
+
+/*
+Routine Description :
+    Releases the resource mapped by netuio_evt_prepare_hw
+
+Return Value :
+    STATUS_SUCCESS always.
+-*/
+NTSTATUS
+netuio_evt_release_hw(_In_ WDFDEVICE Device, _In_ WDFCMRESLIST ResourcesTranslated)
+{
+    UNREFERENCED_PARAMETER(ResourcesTranslated);
+
+    netuio_free_hw_resources(Device);
+
+    return STATUS_SUCCESS;
+}
+
+/*
+Routine Description:
+    Free all the resources allocated in DriverEntry.
+
+Return Value:
+    None
+-*/
+VOID
+netuio_evt_driver_context_cleanup(_In_ WDFOBJECT DriverObject)
+{
+    UNREFERENCED_PARAMETER(DriverObject);
+    DbgPrintEx(DPFLTR_IHVNETWORK_ID, DPFLTR_NETUIO_INFO_LEVEL, "netUIO Driver unloaded.\n");
+    PAGED_CODE();
+}
+
+/*
+Routine Description :
+    EVT_WDF_FILE_CLEANUP callback, called when user process handle is closed.
+
+    Undoes IOCTL_NETUIO_MAP_HW_INTO_USERMODE.
+
+Return value :
+    None
+-*/
+VOID
+netuio_evt_file_cleanup(_In_ WDFFILEOBJECT FileObject)
+{
+    WDFDEVICE device;
+    PNETUIO_CONTEXT_DATA netuio_contextdata;
+
+    device = WdfFileObjectGetDevice(FileObject);
+    netuio_contextdata = netuio_get_context_data(device);
+
+    if (netuio_contextdata)
+    {
+        netuio_unmap_address_from_user_process(netuio_contextdata);
+    }
+}
diff --git a/windows/netuio/kernel/windows/netuio/netuio_drv.h b/windows/netuio/kernel/windows/netuio/netuio_drv.h
new file mode 100644
index 000000000..07743bf86
--- /dev/null
+++ b/windows/netuio/kernel/windows/netuio/netuio_drv.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) Microsoft Corporation. All rights reserved
+ */
+
+
+#ifndef NETUIO_DRV_H
+#define NETUIO_DRV_H
+
+#define INITGUID
+
+#include <ntddk.h>
+#include <wdf.h>
+
+#include "netuio_dev.h"
+#include "netuio_queue.h"
+
+EXTERN_C_START
+
+// Print output constants
+#define DPFLTR_NETUIO_INFO_LEVEL   35
+
+// WDFDRIVER Events
+DRIVER_INITIALIZE DriverEntry;
+EVT_WDF_DRIVER_DEVICE_ADD       netuio_evt_device_add;
+EVT_WDF_OBJECT_CONTEXT_CLEANUP  netuio_evt_driver_context_cleanup;
+EVT_WDF_DEVICE_PREPARE_HARDWARE netuio_evt_prepare_hw;
+EVT_WDF_DEVICE_RELEASE_HARDWARE netuio_evt_release_hw;
+EVT_WDF_FILE_CLOSE              netuio_evt_file_cleanup;
+
+EXTERN_C_END
+
+#endif // NETUIO_DRV_H
diff --git a/windows/netuio/kernel/windows/netuio/netuio_interface.h b/windows/netuio/kernel/windows/netuio/netuio_interface.h
new file mode 100644
index 000000000..6674931d2
--- /dev/null
+++ b/windows/netuio/kernel/windows/netuio/netuio_interface.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) Microsoft Corporation. All rights reserved
+ */
+
+#ifndef NETUIO_INTERFACE_H
+#define NETUIO_INTERFACE_H
+
+// All structures in this file are packed on an 8B boundary. 
+#pragma pack(push)
+#pragma pack(8)
+
+// Define an Interface Guid so that any app can find the device and talk to it.
+DEFINE_GUID (GUID_DEVINTERFACE_netUIO, 0x08336f60,0x0679,0x4c6c,0x85,0xd2,0xae,0x7c,0xed,0x65,0xff,0xf7); // {08336f60-0679-4c6c-85d2-ae7ced65fff7}
+
+// Device name definitions
+#define NETUIO_DEVICE_SYMBOLIC_LINK_ANSI    "\\DosDevices\\netuio"
+
+// netUIO driver symbolic name (prefix)
+#define NETUIO_DRIVER_NAME  _T("netuio")
+
+// IOCTL code definitions
+#define IOCTL_NETUIO_MAP_HW_INTO_USERMODE CTL_CODE(FILE_DEVICE_NETWORK, 51, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
+#define IOCTL_NETUIO_PCI_CONFIG_IO        CTL_CODE(FILE_DEVICE_NETWORK, 52, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
+
+struct mem_region {
+    UINT64           size;       // memory region size
+    PHYSICAL_ADDRESS phys_addr;  // physical address of the memory region
+    PVOID            virt_addr;  // virtual address of the memory region
+    PVOID            user_mapped_virt_addr;  // virtual address of the region mapped into user process context
+};
+
+struct dev_addr {
+    ULONG   bus_num;
+    USHORT  dev_num;
+    USHORT  func_num;
+};
+
+enum pci_io {
+    PCI_IO_READ = 0,
+    PCI_IO_WRITE = 1
+};
+
+#define PCI_MAX_BAR 6
+
+struct dpdk_private_info
+{
+    struct mem_region   hw[PCI_MAX_BAR];
+    struct mem_region   ms;
+    struct dev_addr     dev_addr;
+    UINT16              dev_id;
+    UINT16              sub_dev_id;
+    USHORT              dev_numa_node;
+    USHORT              reserved;
+};
+
+struct dpdk_pci_config_io
+{
+    struct dev_addr     dev_addr;
+    UINT32              offset;
+    enum pci_io         op;
+    UINT32              access_size; // 1, 2, 4, or 8 bytes
+
+    union dpdk_pci_config_io_data {
+        UINT8			u8;
+        UINT16			u16;
+        UINT32			u32;
+        UINT64			u64;
+    } data;
+};
+
+#pragma pack(pop)
+
+#endif // NETUIO_INTERFACE_H
diff --git a/windows/netuio/kernel/windows/netuio/netuio_queue.c b/windows/netuio/kernel/windows/netuio/netuio_queue.c
new file mode 100644
index 000000000..a2d16c417
--- /dev/null
+++ b/windows/netuio/kernel/windows/netuio/netuio_queue.c
@@ -0,0 +1,397 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) Microsoft Corporation. All rights reserved
+ */
+
+#include "netuio_drv.h"
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text (PAGE, netuio_queue_initialize)
+#endif
+
+static void
+netuio_handle_get_hw_data_request(_In_ WDFREQUEST Request, _In_ PNETUIO_CONTEXT_DATA netuio_contextdata,
+                                   _In_ PVOID outputBuf, _In_ size_t outputBufSize)
+{
+    WDF_REQUEST_PARAMETERS params;
+    WDF_REQUEST_PARAMETERS_INIT(&params);
+    WdfRequestGetParameters(Request, &params);
+
+    ASSERT(outputBufSize == sizeof(struct dpdk_private_info));
+
+    struct dpdk_private_info *dpdk_pvt_info = (struct dpdk_private_info *)outputBuf;
+    RtlZeroMemory(dpdk_pvt_info, outputBufSize);
+
+    for (ULONG idx = 0; idx < PCI_MAX_BAR; idx++) {
+        dpdk_pvt_info->hw[idx].phys_addr.QuadPart = netuio_contextdata->bar[idx].base_addr.QuadPart;
+        dpdk_pvt_info->hw[idx].user_mapped_virt_addr = netuio_contextdata->dpdk_hw[idx].mem.user_mapped_virt_addr;
+        dpdk_pvt_info->hw[idx].size = netuio_contextdata->bar[idx].size;
+    }
+
+    dpdk_pvt_info->ms.phys_addr.QuadPart = netuio_contextdata->dpdk_seg.mem.phys_addr.QuadPart;
+    dpdk_pvt_info->ms.user_mapped_virt_addr = netuio_contextdata->dpdk_seg.mem.user_mapped_virt_addr;
+    dpdk_pvt_info->ms.size = netuio_contextdata->dpdk_seg.mem.size;
+}
+
+/*
+Routine Description:
+    Maps address ranges into the usermode process's address space.  The following
+    ranges are mapped:
+
+        * Any PCI BARs that our device was assigned
+        * The scratch buffer of contiguous pages
+
+Return Value:
+    NTSTATUS
+*/
+static NTSTATUS
+netuio_map_address_into_user_process(_In_ PNETUIO_CONTEXT_DATA netuio_contextdata)
+{
+    NTSTATUS status = STATUS_SUCCESS;
+
+    // Map the scratch memory regions to the user's process context
+    MmBuildMdlForNonPagedPool(netuio_contextdata->dpdk_seg.mdl);
+    __try {
+        netuio_contextdata->dpdk_seg.mem.user_mapped_virt_addr =
+            MmMapLockedPagesSpecifyCache(netuio_contextdata->dpdk_seg.mdl, UserMode,
+                                         MmCached, NULL, FALSE, NormalPagePriority);
+
+        if (netuio_contextdata->dpdk_seg.mem.user_mapped_virt_addr == NULL) {
+            status = STATUS_INSUFFICIENT_RESOURCES;
+            goto end;
+        }
+    }
+    __except (EXCEPTION_EXECUTE_HANDLER) {
+        status = GetExceptionCode();
+        goto end;
+    }
+
+    // Map any device BAR(s) to the user's process context
+    for (INT idx = 0; idx < PCI_MAX_BAR; idx++) {
+        if (netuio_contextdata->dpdk_hw[idx].mdl == NULL) {
+            continue;
+        }
+
+        MmBuildMdlForNonPagedPool(netuio_contextdata->dpdk_hw[idx].mdl);
+        __try {
+            netuio_contextdata->dpdk_hw[idx].mem.user_mapped_virt_addr =
+                MmMapLockedPagesSpecifyCache(netuio_contextdata->dpdk_hw[idx].mdl, UserMode,
+                                             MmCached, NULL, FALSE, NormalPagePriority);
+
+            if (netuio_contextdata->dpdk_hw[idx].mem.user_mapped_virt_addr == NULL) {
+                status = STATUS_INSUFFICIENT_RESOURCES;
+                goto end;
+            }
+        }
+        __except (EXCEPTION_EXECUTE_HANDLER) {
+            status = GetExceptionCode();
+            goto end;
+        }
+    }
+
+end:
+    if (status != STATUS_SUCCESS) {
+		netuio_unmap_address_from_user_process(netuio_contextdata);
+    }
+
+    return status;
+}
+
+/*
+Routine Description:
+    Unmaps all address ranges from the usermode process address space.
+    MUST be called in the context of the same process which created
+    the mapping.
+
+Return Value:
+    None
+ */
+VOID
+netuio_unmap_address_from_user_process(_In_ PNETUIO_CONTEXT_DATA netuio_contextdata)
+{
+    if (netuio_contextdata->dpdk_seg.mem.user_mapped_virt_addr != NULL) {
+        MmUnmapLockedPages(
+            netuio_contextdata->dpdk_seg.mem.user_mapped_virt_addr,
+            netuio_contextdata->dpdk_seg.mdl);
+        netuio_contextdata->dpdk_seg.mem.user_mapped_virt_addr = NULL;
+    }
+
+    for (INT idx = 0; idx < PCI_MAX_BAR; idx++) {
+        if (netuio_contextdata->dpdk_hw[idx].mem.user_mapped_virt_addr != NULL) {
+            MmUnmapLockedPages(
+                netuio_contextdata->dpdk_hw[idx].mem.user_mapped_virt_addr,
+                netuio_contextdata->dpdk_hw[idx].mdl);
+            netuio_contextdata->dpdk_hw[idx].mem.user_mapped_virt_addr = NULL;
+        }
+    }
+}
+
+/*
+Routine Description:
+    The I/O dispatch callbacks for the frameworks device object are configured here.
+    A single default I/O Queue is configured for parallel request processing, and a
+    driver context memory allocation is created to hold our structure QUEUE_CONTEXT.
+
+Return Value:
+    None
+ */
+NTSTATUS
+netuio_queue_initialize(_In_ WDFDEVICE Device)
+{
+    WDFQUEUE queue;
+    NTSTATUS status;
+    WDF_IO_QUEUE_CONFIG    queueConfig;
+
+    PAGED_CODE();
+
+    // Configure a default queue so that requests that are not
+    // configure-fowarded using WdfDeviceConfigureRequestDispatching to goto
+    // other queues get dispatched here.
+    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig, WdfIoQueueDispatchParallel);
+
+    queueConfig.EvtIoDeviceControl = netuio_evt_IO_device_control;
+    queueConfig.EvtIoStop = netuio_evt_IO_stop;
+
+    status = WdfIoQueueCreate(Device,
+                              &queueConfig,
+                              WDF_NO_OBJECT_ATTRIBUTES,
+                              &queue);
+
+    if( !NT_SUCCESS(status) ) {
+        return status;
+    }
+
+    return status;
+}
+
+/*
+Routine Description:
+    This routine is invoked to preprocess an I/O request before being placed into a queue.
+    It is guaranteed that it executes in the context of the process that generated the request.
+
+Return Value:
+    None
+ */
+_Use_decl_annotations_
+VOID
+netuio_evt_IO_in_caller_context(
+    IN WDFDEVICE  Device,
+    IN WDFREQUEST Request
+)
+{
+    WDF_REQUEST_PARAMETERS params = { 0 };
+    NTSTATUS status = STATUS_SUCCESS;
+    PVOID    input_buf = NULL, output_buf = NULL;
+    size_t   input_buf_size, output_buf_size;
+    size_t  bytes_returned = 0;
+    PNETUIO_CONTEXT_DATA  netuio_contextdata = NULL;
+
+    netuio_contextdata = netuio_get_context_data(Device);
+
+    WDF_REQUEST_PARAMETERS_INIT(&params);
+    WdfRequestGetParameters(Request, &params);
+
+    // We only need to be in the context of the process that initiated the request
+    //when we need to map memory to userspace. Otherwise, send the request back to framework.
+    if (!((params.Type == WdfRequestTypeDeviceControl) &&
+        (params.Parameters.DeviceIoControl.IoControlCode == IOCTL_NETUIO_MAP_HW_INTO_USERMODE)))
+    {
+        status = WdfDeviceEnqueueRequest(Device, Request);
+
+        if (!NT_SUCCESS(status))
+        {
+            WdfRequestCompleteWithInformation(Request, status, bytes_returned);
+        }
+        return;
+    }
+
+    // First retrieve the input buffer and see if it matches our device
+    status = WdfRequestRetrieveInputBuffer(Request, sizeof(struct dpdk_private_info), &input_buf, &input_buf_size);
+    if (!NT_SUCCESS(status)) {
+        status = STATUS_INVALID_BUFFER_SIZE;
+        goto end;
+    }
+
+    struct dpdk_private_info* dpdk_pvt_info = (struct dpdk_private_info*)input_buf;
+    // Ensure that the B:D:F match - otherwise, fail the IOCTL
+    if ((netuio_contextdata->addr.bus_num != dpdk_pvt_info->dev_addr.bus_num) ||
+        (netuio_contextdata->addr.dev_num != dpdk_pvt_info->dev_addr.dev_num) ||
+        (netuio_contextdata->addr.func_num != dpdk_pvt_info->dev_addr.func_num)) {
+        status = STATUS_NOT_SAME_DEVICE;
+        goto end;
+    }
+
+    if (netuio_contextdata->dpdk_seg.mem.user_mapped_virt_addr != NULL) {
+        status = STATUS_ALREADY_COMMITTED;
+        goto end;
+    }
+
+    // Return relevant data to the caller
+    status = WdfRequestRetrieveOutputBuffer(Request, sizeof(struct dpdk_private_info), &output_buf, &output_buf_size);
+    if (!NT_SUCCESS(status)) {
+        status = STATUS_INVALID_BUFFER_SIZE;
+        goto end;
+    }
+
+    // Zero out the physically contiguous block
+    RtlZeroMemory(netuio_contextdata->dpdk_seg.mem.virt_addr, netuio_contextdata->dpdk_seg.mem.size);
+
+    status = netuio_map_address_into_user_process(netuio_contextdata);
+    if (status != STATUS_SUCCESS) {
+        goto end;
+    }
+
+    netuio_handle_get_hw_data_request(Request, netuio_contextdata, output_buf, output_buf_size);
+    bytes_returned = output_buf_size;
+
+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
+ */
+VOID
+netuio_evt_IO_device_control(_In_ WDFQUEUE Queue, _In_ WDFREQUEST Request,
+                              _In_ size_t OutputBufferLength, _In_ size_t InputBufferLength,
+                              _In_ ULONG IoControlCode)
+{
+    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);
+
+    PNETUIO_CONTEXT_DATA  netuio_contextdata;
+    netuio_contextdata = netuio_get_context_data(device);
+
+    if (IoControlCode != IOCTL_NETUIO_PCI_CONFIG_IO)
+    {
+        status = STATUS_INVALID_DEVICE_REQUEST;
+        goto end;
+    }
+
+    // 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);
+    if (!NT_SUCCESS(status)) {
+        status = STATUS_INVALID_BUFFER_SIZE;
+        goto end;
+    }
+
+    struct dpdk_pci_config_io *dpdk_pci_io_input = (struct dpdk_pci_config_io *)input_buf;
+
+    if (dpdk_pci_io_input->access_size != 1 &&
+        dpdk_pci_io_input->access_size != 2 &&
+        dpdk_pci_io_input->access_size != 4 &&
+        dpdk_pci_io_input->access_size != 8) {
+        status = STATUS_INVALID_PARAMETER;
+        goto end;
+    }
+
+    // Ensure that the B:D:F match - otherwise, fail the IOCTL
+    if ((netuio_contextdata->addr.bus_num != dpdk_pci_io_input->dev_addr.bus_num) ||
+        (netuio_contextdata->addr.dev_num != dpdk_pci_io_input->dev_addr.dev_num) ||
+        (netuio_contextdata->addr.func_num != dpdk_pci_io_input->dev_addr.func_num)) {
+        status = STATUS_NOT_SAME_DEVICE;
+        goto end;
+    }
+    // Retrieve output buffer
+    status = WdfRequestRetrieveOutputBuffer(Request, sizeof(UINT64), &output_buf, &output_buf_size);
+    if (!NT_SUCCESS(status)) {
+        status = STATUS_INVALID_BUFFER_SIZE;
+        goto end;
+    }
+    ASSERT(output_buf_size == OutputBufferLength);
+
+    if (dpdk_pci_io_input->op == PCI_IO_READ) {
+        *(UINT64 *)output_buf = 0;
+        bytes_returned = netuio_contextdata->bus_interface.GetBusData(
+            netuio_contextdata->bus_interface.Context,
+            PCI_WHICHSPACE_CONFIG,
+            output_buf,
+            dpdk_pci_io_input->offset,
+            dpdk_pci_io_input->access_size);
+    }
+    else if (dpdk_pci_io_input->op == PCI_IO_WRITE) {
+        // returns bytes written
+        bytes_returned = netuio_contextdata->bus_interface.SetBusData(
+            netuio_contextdata->bus_interface.Context,
+            PCI_WHICHSPACE_CONFIG,
+            (PVOID)&dpdk_pci_io_input->data,
+            dpdk_pci_io_input->offset,
+            dpdk_pci_io_input->access_size);
+    }
+    else {
+        status = STATUS_INVALID_PARAMETER;
+        goto end;
+    }
+
+end:
+    WdfRequestCompleteWithInformation(Request, status, bytes_returned);
+
+    return;
+}
+
+/*
+Routine Description:
+    This event is invoked for a power-managed queue before the device leaves the working state (D0).
+
+Return Value:
+    None
+ */
+VOID
+netuio_evt_IO_stop(_In_ WDFQUEUE Queue, _In_ WDFREQUEST Request,_In_ ULONG ActionFlags)
+{
+    //
+    // In most cases, the EvtIoStop callback function completes, cancels, or postpones
+    // further processing of the I/O request.
+    //
+    // Typically, the driver uses the following rules:
+    //
+    // - If the driver owns the I/O request, it calls WdfRequestUnmarkCancelable
+    //   (if the request is cancelable) and either calls WdfRequestStopAcknowledge
+    //   with a Requeue value of TRUE, or it calls WdfRequestComplete with a
+    //   completion status value of STATUS_SUCCESS or STATUS_CANCELLED.
+    //
+    //   Before it can call these methods safely, the driver must make sure that
+    //   its implementation of EvtIoStop has exclusive access to the request.
+    //
+    //   In order to do that, the driver must synchronize access to the request
+    //   to prevent other threads from manipulating the request concurrently.
+    //   The synchronization method you choose will depend on your driver's design.
+    //
+    //   For example, if the request is held in a shared context, the EvtIoStop callback
+    //   might acquire an internal driver lock, take the request from the shared context,
+    //   and then release the lock. At this point, the EvtIoStop callback owns the request
+    //   and can safely complete or requeue the request.
+    //
+    // - If the driver has forwarded the I/O request to an I/O target, it either calls
+    //   WdfRequestCancelSentRequest to attempt to cancel the request, or it postpones
+    //   further processing of the request and calls WdfRequestStopAcknowledge with
+    //   a Requeue value of FALSE.
+    //
+    // A driver might choose to take no action in EvtIoStop for requests that are
+    // guaranteed to complete in a small amount of time.
+    //
+    // In this case, the framework waits until the specified request is complete
+    // before moving the device (or system) to a lower power state or removing the device.
+    // Potentially, this inaction can prevent a system from entering its hibernation state
+    // or another low system power state. In extreme cases, it can cause the system
+    // to crash with bugcheck code 9F.
+    //
+    UNREFERENCED_PARAMETER(Queue);
+    UNREFERENCED_PARAMETER(Request);
+    UNREFERENCED_PARAMETER(ActionFlags);
+
+    return;
+}
+
diff --git a/windows/netuio/kernel/windows/netuio/netuio_queue.h b/windows/netuio/kernel/windows/netuio/netuio_queue.h
new file mode 100644
index 000000000..9173a77b9
--- /dev/null
+++ b/windows/netuio/kernel/windows/netuio/netuio_queue.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) Microsoft Corporation. All rights reserved
+ */
+
+#ifndef NETUIO_QUEUE_H
+#define NETUIO_QUEUE_H
+
+EXTERN_C_START
+
+// This is the context that can be placed per queue and would contain per queue information.
+typedef struct _QUEUE_CONTEXT {
+    ULONG PrivateDeviceData;  // just a placeholder
+} QUEUE_CONTEXT, *PQUEUE_CONTEXT;
+
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(QUEUE_CONTEXT, QueueGetContext)
+
+VOID
+netuio_unmap_address_from_user_process(_In_ PNETUIO_CONTEXT_DATA netuio_contextdata);
+
+NTSTATUS
+netuio_queue_initialize(_In_ WDFDEVICE hDevice);
+
+// Events from the IoQueue object
+EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL netuio_evt_IO_device_control;
+EVT_WDF_IO_QUEUE_IO_STOP netuio_evt_IO_stop;
+
+EVT_WDF_IO_IN_CALLER_CONTEXT netuio_evt_IO_in_caller_context;
+
+EXTERN_C_END
+
+#endif // NETUIO_QUEUE_H
diff --git a/windows/netuio/kernel/windows/netuio/resource.h b/windows/netuio/kernel/windows/netuio/resource.h
new file mode 100644
index 000000000..9789ffdf3
--- /dev/null
+++ b/windows/netuio/kernel/windows/netuio/resource.h
@@ -0,0 +1,14 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by netuio.rc
+
+// Next default values for new objects
+// 
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE        101
+#define _APS_NEXT_COMMAND_VALUE         40001
+#define _APS_NEXT_CONTROL_VALUE         1001
+#define _APS_NEXT_SYMED_VALUE           101
+#endif
+#endif
diff --git a/windows/netuio/mk/exec-env/windows/netuio/netuio.sln b/windows/netuio/mk/exec-env/windows/netuio/netuio.sln
new file mode 100644
index 000000000..15c26e6b9
--- /dev/null
+++ b/windows/netuio/mk/exec-env/windows/netuio/netuio.sln
@@ -0,0 +1,24 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.25420.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "netuio", "netuio.vcxproj", "{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|x64 = Debug|x64
+		Release|x64 = Release|x64
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}.Debug|x64.ActiveCfg = Debug|x64
+		{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}.Debug|x64.Build.0 = Debug|x64
+		{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}.Debug|x64.Deploy.0 = Debug|x64
+		{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}.Release|x64.ActiveCfg = Release|x64
+		{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}.Release|x64.Build.0 = Release|x64
+		{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}.Release|x64.Deploy.0 = Release|x64
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal
diff --git a/windows/netuio/mk/exec-env/windows/netuio/netuio.vcxproj b/windows/netuio/mk/exec-env/windows/netuio/netuio.vcxproj
new file mode 100644
index 000000000..fba0f8a1b
--- /dev/null
+++ b/windows/netuio/mk/exec-env/windows/netuio/netuio.vcxproj
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}</ProjectGuid>
+    <TemplateGuid>{497e31cb-056b-4f31-abb8-447fd55ee5a5}</TemplateGuid>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <MinimumVisualStudioVersion>12.0</MinimumVisualStudioVersion>
+    <Configuration>Debug</Configuration>
+    <Platform Condition="'$(Platform)' == ''">Win32</Platform>
+    <RootNamespace>netuio</RootNamespace>
+    <WindowsTargetPlatformVersion>$(LatestTargetPlatformVersion)</WindowsTargetPlatformVersion>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <TargetVersion>
+    </TargetVersion>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
+    <ConfigurationType>Driver</ConfigurationType>
+    <DriverType>KMDF</DriverType>
+    <DriverTargetPlatform>Desktop</DriverTargetPlatform>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <TargetVersion>
+    </TargetVersion>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
+    <ConfigurationType>Driver</ConfigurationType>
+    <DriverType>KMDF</DriverType>
+    <DriverTargetPlatform>Desktop</DriverTargetPlatform>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
+    <OutDir>$(SolutionDir)..\..\..\..\$(Platform)\$(ConfigurationName)\$(MSBuildProjectName)\</OutDir>
+    <IntDir>$(SolutionDir)..\$(Platform)\$(ConfigurationName)\$(MSBuildProjectName)\</IntDir>
+    <Inf2CatUseLocalTime>true</Inf2CatUseLocalTime>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
+    <OutDir>$(SolutionDir)..\..\..\..\$(Platform)\$(ConfigurationName)\$(MSBuildProjectName)\</OutDir>
+    <IntDir>$(SolutionDir)..\$(Platform)\$(ConfigurationName)\$(MSBuildProjectName)\</IntDir>
+    <Inf2CatUseLocalTime>true</Inf2CatUseLocalTime>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <WppEnabled>false</WppEnabled>
+      <WppRecorderEnabled>true</WppRecorderEnabled>
+      <WppScanConfigurationData Condition="'%(ClCompile.ScanConfigurationData)' == ''">trace.h</WppScanConfigurationData>
+      <WppKernelMode>true</WppKernelMode>
+      <WppMinimalRebuildFromTracking>false</WppMinimalRebuildFromTracking>
+    </ClCompile>
+    <Inf>
+      <TimeStamp>0.6.0.5</TimeStamp>
+    </Inf>
+    <Link>
+      <AdditionalDependencies>%(AdditionalDependencies);$(KernelBufferOverflowLib);$(DDK_LIB_PATH)ntoskrnl.lib;$(DDK_LIB_PATH)hal.lib;$(DDK_LIB_PATH)wmilib.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfLdr.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfDriverEntry.lib;$(DDK_LIB_PATH)\wdmsec.lib</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <WppEnabled>false</WppEnabled>
+      <WppRecorderEnabled>true</WppRecorderEnabled>
+      <WppScanConfigurationData Condition="'%(ClCompile.ScanConfigurationData)' == ''">trace.h</WppScanConfigurationData>
+      <WppKernelMode>true</WppKernelMode>
+      <WppMinimalRebuildFromTracking>false</WppMinimalRebuildFromTracking>
+    </ClCompile>
+    <Inf>
+      <TimeStamp>0.6.0.5</TimeStamp>
+    </Inf>
+    <Link>
+      <AdditionalDependencies>%(AdditionalDependencies);$(KernelBufferOverflowLib);$(DDK_LIB_PATH)ntoskrnl.lib;$(DDK_LIB_PATH)hal.lib;$(DDK_LIB_PATH)wmilib.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfLdr.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfDriverEntry.lib;$(DDK_LIB_PATH)\wdmsec.lib</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <FilesToPackage Include="$(TargetPath)" />
+  </ItemGroup>
+  <ItemGroup>
+    <Inf Include="..\..\..\..\kernel\windows\netuio\netuio.inf" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="..\..\..\..\kernel\windows\netuio\netuio_dev.h" />
+    <ClInclude Include="..\..\..\..\kernel\windows\netuio\netuio_drv.h" />
+    <ClInclude Include="..\..\..\..\kernel\windows\netuio\netuio_interface.h" />
+    <ClInclude Include="..\..\..\..\kernel\windows\netuio\netuio_queue.h" />
+    <ClInclude Include="..\..\..\..\kernel\windows\netuio\resource.h" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="..\..\..\..\kernel\windows\netuio\netuio_dev.c" />
+    <ClCompile Include="..\..\..\..\kernel\windows\netuio\netuio_drv.c" />
+    <ClCompile Include="..\..\..\..\kernel\windows\netuio\netuio_queue.c" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>
\ No newline at end of file
diff --git a/windows/netuio/mk/exec-env/windows/netuio/netuio.vcxproj.filters b/windows/netuio/mk/exec-env/windows/netuio/netuio.vcxproj.filters
new file mode 100644
index 000000000..36d8e2a02
--- /dev/null
+++ b/windows/netuio/mk/exec-env/windows/netuio/netuio.vcxproj.filters
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <Filter Include="Source Files">
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+    </Filter>
+    <Filter Include="Header Files">
+      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+      <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
+    </Filter>
+    <Filter Include="Resource Files">
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+    </Filter>
+    <Filter Include="Driver Files">
+      <UniqueIdentifier>{8E41214B-6785-4CFE-B992-037D68949A14}</UniqueIdentifier>
+      <Extensions>inf;inv;inx;mof;mc;</Extensions>
+    </Filter>
+  </ItemGroup>
+  <ItemGroup>
+    <Inf Include="..\..\..\..\kernel\windows\netuio\netuio.inf">
+      <Filter>Driver Files</Filter>
+    </Inf>
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="..\..\..\..\kernel\windows\netuio\netuio_interface.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\..\kernel\windows\netuio\netuio_dev.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\..\kernel\windows\netuio\netuio_drv.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\..\kernel\windows\netuio\netuio_queue.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\..\kernel\windows\netuio\resource.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="..\..\..\..\kernel\windows\netuio\netuio_queue.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\..\kernel\windows\netuio\netuio_dev.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\..\kernel\windows\netuio\netuio_drv.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+  </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/windows/netuio/mk/exec-env/windows/netuio/netuio.vcxproj.user b/windows/netuio/mk/exec-env/windows/netuio/netuio.vcxproj.user
new file mode 100644
index 000000000..771ce8681
--- /dev/null
+++ b/windows/netuio/mk/exec-env/windows/netuio/netuio.vcxproj.user
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <TestCertificate>
+    </TestCertificate>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <TestCertificate>
+    </TestCertificate>
+  </PropertyGroup>
+</Project>
\ No newline at end of file
-- 
2.23.0.vfs.1.1.63.g5a5ad7f


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [dpdk-dev] [PATCH v2] windows/netuio: add Windows NetUIO kernel driver
  2020-08-20 22:23 [dpdk-dev] [PATCH v2] windows/netuio: add Windows NetUIO kernel driver Narcisa Ana Maria Vasile
@ 2020-08-21  1:32 ` Ranjit Menon
  2020-09-09 18:58   ` Narcisa Ana Maria Vasile
  2020-08-24 20:53 ` Dmitry Kozlyuk
  2020-09-09 18:41 ` [dpdk-dev] [PATCH v3] " Narcisa Ana Maria Vasile
  2 siblings, 1 reply; 16+ messages in thread
From: Ranjit Menon @ 2020-08-21  1:32 UTC (permalink / raw)
  To: Narcisa Ana Maria Vasile, dev, thomas, ocardona, haramakr, pallavi.kadam
  Cc: dmitry.kozliuk, dmitrym, Narcisa Vasile, Harini Ramakrishnan

Hi, Naty

On 8/20/2020 3:23 PM, Narcisa Ana Maria Vasile wrote:
> From: Narcisa Vasile <navasile@microsoft.com>
>
> The Windows NetUIO kernel driver allows the DPDK userspace
> application to directly access the hardware.
>
> Cc: Harini Ramakrishnan <Harini.Ramakrishnan@microsoft.com>
> Cc: Omar Cardona <ocardona@microsoft.com>
> Signed-off-by: Narcisa Vasile <navasile@microsoft.com>
> ---
>
> v2:
>    Fix license message in each file
>    Minor fixes in README
>    Remove RC file as it is autogenerated
>    Remove HW Ids from Inf
>    Resubmit as single patch
>
>   .gitattributes                                |   4 +
>   .gitignore                                    |   2 +
>   windows/netuio/kernel/README_NetUIO.rst       |  64 +++
>   .../netuio/kernel/windows/netuio/netuio.inf   |  78 ++++
>   .../netuio/kernel/windows/netuio/netuio_dev.c | 388 +++++++++++++++++
>   .../netuio/kernel/windows/netuio/netuio_dev.h |  61 +++
>   .../netuio/kernel/windows/netuio/netuio_drv.c | 146 +++++++
>   .../netuio/kernel/windows/netuio/netuio_drv.h |  32 ++
>   .../kernel/windows/netuio/netuio_interface.h  |  73 ++++
>   .../kernel/windows/netuio/netuio_queue.c      | 397 ++++++++++++++++++
>   .../kernel/windows/netuio/netuio_queue.h      |  31 ++
>   .../netuio/kernel/windows/netuio/resource.h   |  14 +
>   .../mk/exec-env/windows/netuio/netuio.sln     |  24 ++
>   .../mk/exec-env/windows/netuio/netuio.vcxproj | 113 +++++
>   .../windows/netuio/netuio.vcxproj.filters     |  54 +++
>   .../windows/netuio/netuio.vcxproj.user        |  11 +
>   16 files changed, 1492 insertions(+)
>   create mode 100644 .gitattributes
>   create mode 100644 .gitignore
>   create mode 100644 windows/netuio/kernel/README_NetUIO.rst
>   create mode 100644 windows/netuio/kernel/windows/netuio/netuio.inf
>   create mode 100644 windows/netuio/kernel/windows/netuio/netuio_dev.c
>   create mode 100644 windows/netuio/kernel/windows/netuio/netuio_dev.h
>   create mode 100644 windows/netuio/kernel/windows/netuio/netuio_drv.c
>   create mode 100644 windows/netuio/kernel/windows/netuio/netuio_drv.h
>   create mode 100644 windows/netuio/kernel/windows/netuio/netuio_interface.h
>   create mode 100644 windows/netuio/kernel/windows/netuio/netuio_queue.c
>   create mode 100644 windows/netuio/kernel/windows/netuio/netuio_queue.h
>   create mode 100644 windows/netuio/kernel/windows/netuio/resource.h
>   create mode 100644 windows/netuio/mk/exec-env/windows/netuio/netuio.sln
>   create mode 100644 windows/netuio/mk/exec-env/windows/netuio/netuio.vcxproj
>   create mode 100644 windows/netuio/mk/exec-env/windows/netuio/netuio.vcxproj.filters
>   create mode 100644 windows/netuio/mk/exec-env/windows/netuio/netuio.vcxproj.user

I haven't reviewed the code yet, but a few general suggestions and 
observations:

1. This code is going into the dpdk-kmods repo, so we don't need the 
four-level deep directory path for the source.

2. The code can simply be in the windows/netuio directory (don't need 
'kernel')

3. The 'virt2phys' module that is already in the repo puts the 
build-related files (project files, solution files etc) in the same 
folder as the source files. We should do the same here. We don't need 
the mk/exec-env subfolder for these files. They can be in the same 
directory as the source.

4. Remove the .vcxproj.user file from the patch. It is not required.

5. The README should be renamed to simply README.rst and it can also be 
in this windows/netuio folder

6. The existing README.rst in the windows folder contains information on 
how to build and load kernel modules in Windows. This new README can 
reference the root-level README, if required. (Also some of the 
information in this new README is out-of-date. I'll review that separately)


ranjit m.


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [dpdk-dev] [PATCH v2] windows/netuio: add Windows NetUIO kernel driver
  2020-08-20 22:23 [dpdk-dev] [PATCH v2] windows/netuio: add Windows NetUIO kernel driver Narcisa Ana Maria Vasile
  2020-08-21  1:32 ` Ranjit Menon
@ 2020-08-24 20:53 ` Dmitry Kozlyuk
  2020-09-09 18:53   ` Narcisa Ana Maria Vasile
  2020-09-09 18:41 ` [dpdk-dev] [PATCH v3] " Narcisa Ana Maria Vasile
  2 siblings, 1 reply; 16+ messages in thread
From: Dmitry Kozlyuk @ 2020-08-24 20:53 UTC (permalink / raw)
  To: Narcisa Ana Maria Vasile
  Cc: dev, thomas, ocardona, haramakr, pallavi.kadam, ranjit.menon,
	dmitrym, Narcisa Vasile, Harini Ramakrishnan

On Thu, 20 Aug 2020 15:23:55 -0700, Narcisa Ana Maria Vasile wrote:
> From: Narcisa Vasile <navasile@microsoft.com>
> 
> The Windows NetUIO kernel driver allows the DPDK userspace
> application to directly access the hardware.
> 
> Cc: Harini Ramakrishnan <Harini.Ramakrishnan@microsoft.com>
> Cc: Omar Cardona <ocardona@microsoft.com>
> Signed-off-by: Narcisa Vasile <navasile@microsoft.com>
> ---

Major questions:

1. Does NetUIO still need to allocate and map a contiguous memory segment
now, when DPDK has user-mode memory management?

2. IOCTLs require to specify PCI address on each call. This is very
inconvenient for DPDK consumers and also seems to serve no purpose.

3. There is a need to document driver's design, preferably in commit message,
specifically:

3.1) DMA remapping capability in INF (AFAIK, vendors are notified);
3.2) manual BAR probing instead of using resource lists;
3.3) reason to use EvtIoInCallerContext and IO queues;
3.4) IOCTL format.

Also, I agree with everything Ranjit has noted already.

General suggestions to cleanup the code a bit. We can do it later if you
wish. This also brings up the question, which code style should Windows
kernel code for DPDK follow (off-topic for now).

* Remove boilerplate code and comments generated by VS wizard.
* Place `_Use_decl_annotations` on definitions to make them simpler.
* Limit line length, try using shorted variable names (e.g.
  "netuio_contextdata" may be "context" or "ctx" with no loss).

More specific comments inline.


>  create mode 100644 .gitattributes
>  create mode 100644 .gitignore

Both of these files should be in windows/ directory.

>  create mode 100644 windows/netuio/kernel/windows/netuio/resource.h

This file is not needed.


> diff --git a/windows/netuio/kernel/README_NetUIO.rst b/windows/netuio/kernel/README_NetUIO.rst
> new file mode 100644
> index 000000000..a290fcf20
> --- /dev/null
> +++ b/windows/netuio/kernel/README_NetUIO.rst
> @@ -0,0 +1,64 @@
[snip]
> +Installing netuio.sys Driver for development
> +--------------------------------------------
> +.. note::
> +
> +In a development environment, NetUIO driver can be loaded by enabling test-signing.
> +Please implement adequate precautionary measures before installing a test-signed driver, replacing a signed-driver.

Paragraph under "note" must be indented for RST to peek it up as note
content. Like so:

.. note::

   This line has 3 spaces in the beginning.

[snip]
> diff --git a/windows/netuio/kernel/windows/netuio/netuio.inf b/windows/netuio/kernel/windows/netuio/netuio.inf
> new file mode 100644
> index 000000000..88e90b365
> --- /dev/null
> +++ b/windows/netuio/kernel/windows/netuio/netuio.inf
> @@ -0,0 +1,78 @@
> +; SPDX-License-Identifier: BSD-3-Clause
> +; Copyright (c) Microsoft Corporation. All rights reserved

This copyright differs from the one in README.
"All rights reserved" is probably unnecessary.

[snip]
> +[Strings]
> +SPSVCINST_ASSOCSERVICE= 0x00000002
> +Intel = "Intel"
> +Broadcom = "Broadcom Corporation"

IHVs are supposed to add this gradually.

> +ClassName = "Windows UIO"
> +DiskName = "DPDK netUIO Installation Disk"
> +netuio.DeviceDesc = "netuio Device"
> +netuio.SVCDESC = "netuio Service"
> +
> +[DMAr.reg]
> +HKR,Parameters,DmaRemappingCompatible,0x00010001,1
> diff --git a/windows/netuio/kernel/windows/netuio/netuio_dev.c b/windows/netuio/kernel/windows/netuio/netuio_dev.c
> new file mode 100644
> index 000000000..6394bb5d1
> --- /dev/null
> +++ b/windows/netuio/kernel/windows/netuio/netuio_dev.c
[snip]
> +NTSTATUS
> +netuio_create_device(_Inout_ PWDFDEVICE_INIT DeviceInit)
> +{
[snip]
> +
> +    status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);
> +
> +	if (NT_SUCCESS(status)) {
> +		// Create a device interface so that applications can find and talk to us.
> +		status = WdfDeviceCreateDeviceInterface(device, &GUID_DEVINTERFACE_netUIO, NULL);
> +	}

Mixed tabs and spaces for indent.

[snip]
> +/*
> +Routine Description:
> +    Free all the resources allocated in AdfEvtDeviceAdd.

Typo: Adf -> Wdf.

[snip]
> +static NTSTATUS
> +get_pci_device_info(_In_ WDFOBJECT device)
> +{
[snip]
> +        // Also, retrieve the NUMA node of the device
> +        USHORT numaNode;
> +        status = IoGetDeviceNumaNode(pdo, &numaNode);
> +        if (NT_SUCCESS(status)) {
> +            netuio_contextdata->dev_numa_node = numaNode;
> +        }

Why is this needed? Userspace has access to this info.

> +    }
> +
> +    return status;
> +}
> +
> +static NTSTATUS
> +create_device_specific_symbolic_link(_In_ WDFOBJECT device)
> +{
> +    NTSTATUS status = STATUS_UNSUCCESSFUL;
> +    UNICODE_STRING netuio_symbolic_link;
> +
> +    PNETUIO_CONTEXT_DATA  netuio_contextdata;
> +    netuio_contextdata = netuio_get_context_data(device);
> +
> +    if (!netuio_contextdata)
> +        return status;
> +
> +    // Build symbolic link name as <netuio_symbolic_link>_BDF  (bus/device/func)
> +    CHAR  symbolic_link[64] = { 0 };
> +    sprintf_s(symbolic_link, sizeof(symbolic_link), "%s_%04d%02d%02d",
> +                            NETUIO_DEVICE_SYMBOLIC_LINK_ANSI, netuio_contextdata->addr.bus_num,
> +                            netuio_contextdata->addr.dev_num, netuio_contextdata->addr.func_num);
> +
> +    ANSI_STRING ansi_symbolic_link;
> +    RtlInitAnsiString(&ansi_symbolic_link, symbolic_link);
> +
> +    status = RtlAnsiStringToUnicodeString(&netuio_symbolic_link, &ansi_symbolic_link, TRUE);
> +    if (!NT_SUCCESS(status))
> +        return status;

Why not use Unicode directly?

> +
> +    status = WdfDeviceCreateSymbolicLink(device, &netuio_symbolic_link);
> +
> +    RtlFreeUnicodeString(&netuio_symbolic_link);
> +
> +    return status;
> +}
[snip]

> +
> +// Local function protoyypes
> +static NTSTATUS get_pci_device_info(_In_ WDFOBJECT device);
> +static NTSTATUS create_device_specific_symbolic_link(_In_ WDFOBJECT device);
> +static NTSTATUS allocate_usermemory_segment(_In_ WDFOBJECT device);
> +static VOID free_usermemory_segment(_In_ WDFOBJECT device);

Nit: local functions usually don't appear in headers.

[snip]
> diff --git a/windows/netuio/kernel/windows/netuio/netuio_interface.h b/windows/netuio/kernel/windows/netuio/netuio_interface.h
> new file mode 100644
> index 000000000..6674931d2
> --- /dev/null
> +++ b/windows/netuio/kernel/windows/netuio/netuio_interface.h
> @@ -0,0 +1,73 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright (c) Microsoft Corporation. All rights reserved
> + */
> +
> +#ifndef NETUIO_INTERFACE_H
> +#define NETUIO_INTERFACE_H
> +
> +// All structures in this file are packed on an 8B boundary. 
> +#pragma pack(push)
> +#pragma pack(8)
> +
> +// Define an Interface Guid so that any app can find the device and talk to it.
> +DEFINE_GUID (GUID_DEVINTERFACE_netUIO, 0x08336f60,0x0679,0x4c6c,0x85,0xd2,0xae,0x7c,0xed,0x65,0xff,0xf7); // {08336f60-0679-4c6c-85d2-ae7ced65fff7}
> +
> +// Device name definitions
> +#define NETUIO_DEVICE_SYMBOLIC_LINK_ANSI    "\\DosDevices\\netuio"
> +
> +// netUIO driver symbolic name (prefix)
> +#define NETUIO_DRIVER_NAME  _T("netuio")
> +
> +// IOCTL code definitions
> +#define IOCTL_NETUIO_MAP_HW_INTO_USERMODE CTL_CODE(FILE_DEVICE_NETWORK, 51, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
> +#define IOCTL_NETUIO_PCI_CONFIG_IO        CTL_CODE(FILE_DEVICE_NETWORK, 52, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)

Input and output of these interfaces has to be documented somewhere.

> +
> +struct mem_region {
> +    UINT64           size;       // memory region size
> +    PHYSICAL_ADDRESS phys_addr;  // physical address of the memory region
> +    PVOID            virt_addr;  // virtual address of the memory region
> +    PVOID            user_mapped_virt_addr;  // virtual address of the region mapped into user process context
> +};
> +
> +struct dev_addr {
> +    ULONG   bus_num;
> +    USHORT  dev_num;
> +    USHORT  func_num;
> +};
> +
> +enum pci_io {
> +    PCI_IO_READ = 0,
> +    PCI_IO_WRITE = 1
> +};
> +
> +#define PCI_MAX_BAR 6
> +
> +struct dpdk_private_info

Nit: "private" usually means something hidden, not exposed like this. How
about "device_info", "device_data", or "device_resources"?

> +{
> +    struct mem_region   hw[PCI_MAX_BAR];
> +    struct mem_region   ms;
> +    struct dev_addr     dev_addr;

Why is the address needed? Each device has its own symlink and handle.
Such interface is harder to use with no visible benefit. Same below.

> +    UINT16              dev_id;
> +    UINT16              sub_dev_id;
> +    USHORT              dev_numa_node;
> +    USHORT              reserved;
> +};
> +
> +struct dpdk_pci_config_io
> +{
> +    struct dev_addr     dev_addr;
> +    UINT32              offset;
> +    enum pci_io         op;

Driver API should use fixed-size types. Enum size is unspecified.

> +    UINT32              access_size; // 1, 2, 4, or 8 bytes
> +
> +    union dpdk_pci_config_io_data {
> +        UINT8			u8;
> +        UINT16			u16;
> +        UINT32			u32;
> +        UINT64			u64;
> +    } data;
> +};
> +
> +#pragma pack(pop)
> +
> +#endif // NETUIO_INTERFACE_H

[snip]

> +static NTSTATUS
> +netuio_map_address_into_user_process(_In_ PNETUIO_CONTEXT_DATA netuio_contextdata)
> +{
> +    NTSTATUS status = STATUS_SUCCESS;
> +
> +    // Map the scratch memory regions to the user's process context
> +    MmBuildMdlForNonPagedPool(netuio_contextdata->dpdk_seg.mdl);
> +    __try {
> +        netuio_contextdata->dpdk_seg.mem.user_mapped_virt_addr =
> +            MmMapLockedPagesSpecifyCache(netuio_contextdata->dpdk_seg.mdl, UserMode,
> +                                         MmCached, NULL, FALSE, NormalPagePriority);

If user does IOCTL twice, they'll get the same memory mapped twice to
different addresses, but the driver will only remember the last mapping. After
it is removed, the first mapping will remain, exposing kernel memory for
user-after-free from userspace. Or do I miss something here?

DPDK doesn't need multiple mappings, so you could either return an error or
just an already mapped address on the second call.

[snip]

> +NTSTATUS
> +netuio_queue_initialize(_In_ WDFDEVICE Device)
> +{
> +    WDFQUEUE queue;
> +    NTSTATUS status;
> +    WDF_IO_QUEUE_CONFIG    queueConfig;
> +
> +    PAGED_CODE();
> +
> +    // Configure a default queue so that requests that are not
> +    // configure-fowarded using WdfDeviceConfigureRequestDispatching to goto
> +    // other queues get dispatched here.
> +    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig, WdfIoQueueDispatchParallel);
> +
> +    queueConfig.EvtIoDeviceControl = netuio_evt_IO_device_control;
> +    queueConfig.EvtIoStop = netuio_evt_IO_stop;

EvtIoStop is not needed.

[snip]

> +_Use_decl_annotations_
> +VOID
> +netuio_evt_IO_in_caller_context(
> +    IN WDFDEVICE  Device,
> +    IN WDFREQUEST Request
> +)
> +{
> +    WDF_REQUEST_PARAMETERS params = { 0 };
> +    NTSTATUS status = STATUS_SUCCESS;
> +    PVOID    input_buf = NULL, output_buf = NULL;
> +    size_t   input_buf_size, output_buf_size;
> +    size_t  bytes_returned = 0;
> +    PNETUIO_CONTEXT_DATA  netuio_contextdata = NULL;
> +
> +    netuio_contextdata = netuio_get_context_data(Device);
> +
> +    WDF_REQUEST_PARAMETERS_INIT(&params);
> +    WdfRequestGetParameters(Request, &params);
> +
> +    // We only need to be in the context of the process that initiated the request
> +    //when we need to map memory to userspace. Otherwise, send the request back to framework.
> +    if (!((params.Type == WdfRequestTypeDeviceControl) &&
> +        (params.Parameters.DeviceIoControl.IoControlCode == IOCTL_NETUIO_MAP_HW_INTO_USERMODE)))
> +    {
> +        status = WdfDeviceEnqueueRequest(Device, Request);
> +
> +        if (!NT_SUCCESS(status))
> +        {
> +            WdfRequestCompleteWithInformation(Request, status, bytes_returned);

Nit: WdfRequestComplete(Request, status) would be sufficient.

> +        }
> +        return;
> +    }
> +
[snip]

> diff --git a/windows/netuio/kernel/windows/netuio/netuio_queue.h b/windows/netuio/kernel/windows/netuio/netuio_queue.h
> new file mode 100644
> index 000000000..9173a77b9
> --- /dev/null
> +++ b/windows/netuio/kernel/windows/netuio/netuio_queue.h
> @@ -0,0 +1,31 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright (c) Microsoft Corporation. All rights reserved
> + */
> +
> +#ifndef NETUIO_QUEUE_H
> +#define NETUIO_QUEUE_H
> +
> +EXTERN_C_START

Not needed in C code.

> +
> +// This is the context that can be placed per queue and would contain per queue information.
> +typedef struct _QUEUE_CONTEXT {
> +    ULONG PrivateDeviceData;  // just a placeholder
> +} QUEUE_CONTEXT, *PQUEUE_CONTEXT;
> +
> +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(QUEUE_CONTEXT, QueueGetContext)

Boilerplate, not needed.

[snip]


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [dpdk-dev] [PATCH v3] windows/netuio: add Windows NetUIO kernel driver
  2020-08-20 22:23 [dpdk-dev] [PATCH v2] windows/netuio: add Windows NetUIO kernel driver Narcisa Ana Maria Vasile
  2020-08-21  1:32 ` Ranjit Menon
  2020-08-24 20:53 ` Dmitry Kozlyuk
@ 2020-09-09 18:41 ` Narcisa Ana Maria Vasile
  2020-09-13 21:39   ` Dmitry Kozlyuk
  2020-09-19  2:52   ` [dpdk-dev] [PATCH v4] " Narcisa Ana Maria Vasile
  2 siblings, 2 replies; 16+ messages in thread
From: Narcisa Ana Maria Vasile @ 2020-09-09 18:41 UTC (permalink / raw)
  To: dev, thomas, ocardona, haramakr, pallavi.kadam, dmitry.kozliuk
  Cc: ranjit.menon, dmitrym, Narcisa Vasile, Harini Ramakrishnan

From: Narcisa Vasile <navasile@microsoft.com>

The Windows netuio kernel driver provides the DPDK userspace application
with direct access to hardware, by mapping the HW registers in userspace
and allowing read/write operations from/to the device
configuration space.

Two IOCTLs are defined by the netuio interface:
  * IOCTL_NETUIO_MAP_HW_INTO_USERMODE
      - used for mapping the device registers into userspace

      Description of output:
      -	the physical address, virtual address and the size of the
        memory region where the BARs were mapped.

  * IOCTL_NETUIO_PCI_CONFIG_IO
      - used to read/write from/into the device configuration space

      Description of input:
      -	the operation type (read/write)
      -	the offset into the device data where the operation begins
      -	the length of data to read/write.
      -	in case of a write operation, the data to be written to
        the device configuration space.
      Description of output:
      -	in case of a read operation, the output buffer is filled
        with the data read from the configuration space.

Note:
Requests to map the device BARs into userspace need to be processed
in the thread context of the process that initiated the mapping request.
Otherwise, the BARs might end up mapped into an arbitrary process
address space.
EvtIoInCallerContext is used to ensure the requests are handled
in the right user thread context. Other requests (PCI config)
are sent back to the framework and processed by the EvtIoDeviceControl
callback.

Cc: Harini Ramakrishnan <Harini.Ramakrishnan@microsoft.com>
Cc: Omar Cardona <ocardona@microsoft.com>
Cc: Dmitry Malloy <dmitrym@microsoft.com>
Signed-off-by: Narcisa Vasile <navasile@microsoft.com>
---

v3:
* Remove memory segment allocation and mapping logic,
  since DPDK handles memory management
* Remove the PCI address from IOCTL calls
* Add check to verify if device registers are already mapped
  to usermode to avoid overwriting previous mappings
* Change enum to UINT8
* Remove dev_id and dev_numa_node from the interface
* Move source files and Visual Studio files to windows\netuio
* Remove resource.h and netuio.vcxproj.user as they get generated by VS
* Rename the README, add reference to the README in the parent folder,
  minor fixes
* Place _Use_decl_annotations_ on function definitions
* Move git files inside windows folder
* Various cleanups: renaming variables, removing dead code, fixing license text


 windows/.gitattributes                |   4 +
 windows/.gitignore                    |   2 +
 windows/netuio/README.rst             |  58 +++++
 windows/netuio/netuio.inf             |  77 ++++++
 windows/netuio/netuio.sln             |  24 ++
 windows/netuio/netuio.vcxproj         | 113 +++++++++
 windows/netuio/netuio.vcxproj.filters |  54 +++++
 windows/netuio/netuio_dev.c           | 286 ++++++++++++++++++++++
 windows/netuio/netuio_dev.h           |  50 ++++
 windows/netuio/netuio_drv.c           | 158 ++++++++++++
 windows/netuio/netuio_drv.h           |  27 +++
 windows/netuio/netuio_interface.h     |  67 ++++++
 windows/netuio/netuio_queue.c         | 333 ++++++++++++++++++++++++++
 windows/netuio/netuio_queue.h         |  19 ++
 14 files changed, 1272 insertions(+)
 create mode 100644 windows/.gitattributes
 create mode 100644 windows/.gitignore
 create mode 100644 windows/netuio/README.rst
 create mode 100644 windows/netuio/netuio.inf
 create mode 100644 windows/netuio/netuio.sln
 create mode 100644 windows/netuio/netuio.vcxproj
 create mode 100644 windows/netuio/netuio.vcxproj.filters
 create mode 100644 windows/netuio/netuio_dev.c
 create mode 100644 windows/netuio/netuio_dev.h
 create mode 100644 windows/netuio/netuio_drv.c
 create mode 100644 windows/netuio/netuio_drv.h
 create mode 100644 windows/netuio/netuio_interface.h
 create mode 100644 windows/netuio/netuio_queue.c
 create mode 100644 windows/netuio/netuio_queue.h

diff --git a/windows/.gitattributes b/windows/.gitattributes
new file mode 100644
index 000000000..13482db3d
--- /dev/null
+++ b/windows/.gitattributes
@@ -0,0 +1,4 @@
+* text=auto
+
+*.sln text eol=crlf
+*.vcxproj text eol=crlf
diff --git a/windows/.gitignore b/windows/.gitignore
new file mode 100644
index 000000000..543281e8f
--- /dev/null
+++ b/windows/.gitignore
@@ -0,0 +1,2 @@
+x64/
+.vs/
diff --git a/windows/netuio/README.rst b/windows/netuio/README.rst
new file mode 100644
index 000000000..68d53a810
--- /dev/null
+++ b/windows/netuio/README.rst
@@ -0,0 +1,58 @@
+..  SPDX-License-Identifier: BSD-3-Clause
+    Copyright(c) 2020 Microsoft Corporation.
+
+Compiling the NetUIO Driver from Source
+=======================================
+
+Operating System
+~~~~~~~~~~~~~~~~
+
+The NetUIO source has been validated against the following operating systems:
+
+* Windows Server 2016
+* Windows Server 2019
+
+Hardware Requirements
+~~~~~~~~~~~~~~~~~~~~~
+The NetUIO driver has been validated using the following network adapters on the Windows platform:
+
+*
+*
+
+Software Requirements
+~~~~~~~~~~~~~~~~~~~~~
+For a list of required software tools please see the Prerequisites section in windows/README.rst.
+
+Building the NetUIO Driver
+--------------------------
+
+Follow the steps below to build the NetUIO driver and install the driver for the network adapter. 
+
+* Clone the dpdk-kmods repository: git clone git://dpdk.org/dpdk-kmods
+* Navigate to \dpdk-kmods\windows\netuio
+* Load netuio.sln in Microsoft Visual Studio 2017 or 2019
+* Choose Release as the configuration mode and Build the solution
+* The resultant output files can be found in x64\Release\netuio
+ 
+Installing netuio.sys Driver for development
+--------------------------------------------
+.. note::
+
+   In a development environment, NetUIO driver can be loaded by enabling test-signing.
+   Please implement adequate precautionary measures before installing a test-signed driver, replacing a signed-driver.
+
+To ensure test-signed kernel-mode drivers can load on Windows, enable test-signing, using the following BCDEdit command.
+
+C:\windows\system32>Bcdedit.exe -set TESTSIGNING ON
+
+Windows displays the text “Test Mode” to remind users the system has test-signing enabled. 
+Refer to the MSDN documentation on how to Test-Sign a Driver Package.
+ 
+To procure a WHQL signed NetUIO driver for Windows, please reach out to dpdkwin@microsoft.com
+
+* Go to Device Manager -> Network Adapters.
+* Right Click on target network adapter -> Select Update Driver.
+* Select "Browse my computer for driver software".
+* In the resultant window, select "Let me pick from a list of available drivers on my computer".
+* Select "DPDK netUIO for Network Adapter" from the list of drivers.
+* The NetUIO.sys driver is successfully installed.
diff --git a/windows/netuio/netuio.inf b/windows/netuio/netuio.inf
new file mode 100644
index 000000000..7121a0fa0
--- /dev/null
+++ b/windows/netuio/netuio.inf
@@ -0,0 +1,77 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright (c) Microsoft Corporation. All rights reserved
+;
+; netuio.inf
+;
+
+[Version]
+Signature="$WINDOWS NT$"
+Class=%ClassName%
+ClassGuid={78912BC1-CB8E-4B28-A329-F322EBADBE0F}
+Provider=%Provider%
+CatalogFile=netuio.cat
+DriverVer=
+
+[ClassInstall32]
+Addreg=netuioClassReg
+
+[netuioClassReg]
+HKR,,,0,%ClassName%
+HKR,,Icon,,-5
+
+;*****************************************
+; Install Section
+;*****************************************
+
+[Manufacturer]
+%ManufacturerName%=Standard, NT$ARCH$
+
+[Standard.NT$ARCH$]
+%netuio.DeviceDesc%=netuio_Device, Root\netuio
+
+[netuio_Device.NT]
+CopyFiles=Drivers_Dir
+
+[Drivers_Dir]
+netuio.sys
+
+[netuio_Device.NT.HW]
+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)"
+
+;-------------- Service installation
+[netuio_Device.NT.Services]
+AddService = netuio,%SPSVCINST_ASSOCSERVICE%, netuio_Service_Inst
+
+; -------------- netuio driver install sections
+[netuio_Service_Inst]
+DisplayName    = %netuio.SVCDESC%
+ServiceType    = 1               ; SERVICE_KERNEL_DRIVER
+StartType      = 3               ; SERVICE_DEMAND_START
+ErrorControl   = 1               ; SERVICE_ERROR_NORMAL
+ServiceBinary  = %12%\netuio.sys
+AddReg         = DMAr.reg
+
+[DestinationDirs]
+DefaultDestDir = 12
+
+[SourceDisksNames]
+1 = %DiskName%,,,""
+
+[SourceDisksFiles]
+netuio.sys  = 1,,
+
+[Strings]
+SPSVCINST_ASSOCSERVICE= 0x00000002
+ManufacturerName = "Microsoft"
+Provider = "Vendor"
+ClassName = "Windows UIO"
+DiskName = "DPDK netUIO Installation Disk"
+netuio.DeviceDesc = "netuio Device"
+netuio.SVCDESC = "netuio Service"
+
+[DMAr.reg]
+HKR,Parameters,DmaRemappingCompatible,0x00010001,1
diff --git a/windows/netuio/netuio.sln b/windows/netuio/netuio.sln
new file mode 100644
index 000000000..15c26e6b9
--- /dev/null
+++ b/windows/netuio/netuio.sln
@@ -0,0 +1,24 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.25420.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "netuio", "netuio.vcxproj", "{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|x64 = Debug|x64
+		Release|x64 = Release|x64
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}.Debug|x64.ActiveCfg = Debug|x64
+		{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}.Debug|x64.Build.0 = Debug|x64
+		{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}.Debug|x64.Deploy.0 = Debug|x64
+		{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}.Release|x64.ActiveCfg = Release|x64
+		{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}.Release|x64.Build.0 = Release|x64
+		{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}.Release|x64.Deploy.0 = Release|x64
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal
diff --git a/windows/netuio/netuio.vcxproj b/windows/netuio/netuio.vcxproj
new file mode 100644
index 000000000..87c8b940c
--- /dev/null
+++ b/windows/netuio/netuio.vcxproj
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}</ProjectGuid>
+    <TemplateGuid>{497e31cb-056b-4f31-abb8-447fd55ee5a5}</TemplateGuid>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <MinimumVisualStudioVersion>12.0</MinimumVisualStudioVersion>
+    <Configuration>Debug</Configuration>
+    <Platform Condition="'$(Platform)' == ''">Win32</Platform>
+    <RootNamespace>netuio</RootNamespace>
+    <WindowsTargetPlatformVersion>$(LatestTargetPlatformVersion)</WindowsTargetPlatformVersion>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <TargetVersion>
+    </TargetVersion>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
+    <ConfigurationType>Driver</ConfigurationType>
+    <DriverType>KMDF</DriverType>
+    <DriverTargetPlatform>Desktop</DriverTargetPlatform>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <TargetVersion>
+    </TargetVersion>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
+    <ConfigurationType>Driver</ConfigurationType>
+    <DriverType>KMDF</DriverType>
+    <DriverTargetPlatform>Desktop</DriverTargetPlatform>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
+    <OutDir>$(SolutionDir)$(Platform)\$(ConfigurationName)\$(MSBuildProjectName)\</OutDir>
+    <IntDir>$(SolutionDir)$(Platform)\$(ConfigurationName)\$(MSBuildProjectName)\</IntDir>
+    <Inf2CatUseLocalTime>true</Inf2CatUseLocalTime>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
+    <OutDir>$(SolutionDir)$(Platform)\$(ConfigurationName)\$(MSBuildProjectName)\</OutDir>
+    <IntDir>$(SolutionDir)$(Platform)\$(ConfigurationName)\$(MSBuildProjectName)\</IntDir>
+    <Inf2CatUseLocalTime>true</Inf2CatUseLocalTime>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <WppEnabled>false</WppEnabled>
+      <WppRecorderEnabled>true</WppRecorderEnabled>
+      <WppScanConfigurationData Condition="'%(ClCompile.ScanConfigurationData)' == ''">trace.h</WppScanConfigurationData>
+      <WppKernelMode>true</WppKernelMode>
+      <WppMinimalRebuildFromTracking>false</WppMinimalRebuildFromTracking>
+    </ClCompile>
+    <Inf>
+      <TimeStamp>0.6.1</TimeStamp>
+    </Inf>
+    <Link>
+      <AdditionalDependencies>%(AdditionalDependencies);$(KernelBufferOverflowLib);$(DDK_LIB_PATH)ntoskrnl.lib;$(DDK_LIB_PATH)hal.lib;$(DDK_LIB_PATH)wmilib.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfLdr.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfDriverEntry.lib;$(DDK_LIB_PATH)\wdmsec.lib</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <WppEnabled>false</WppEnabled>
+      <WppRecorderEnabled>true</WppRecorderEnabled>
+      <WppScanConfigurationData Condition="'%(ClCompile.ScanConfigurationData)' == ''">trace.h</WppScanConfigurationData>
+      <WppKernelMode>true</WppKernelMode>
+      <WppMinimalRebuildFromTracking>false</WppMinimalRebuildFromTracking>
+    </ClCompile>
+    <Inf>
+      <TimeStamp>0.6.1</TimeStamp>
+    </Inf>
+    <Link>
+      <AdditionalDependencies>%(AdditionalDependencies);$(KernelBufferOverflowLib);$(DDK_LIB_PATH)ntoskrnl.lib;$(DDK_LIB_PATH)hal.lib;$(DDK_LIB_PATH)wmilib.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfLdr.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfDriverEntry.lib;$(DDK_LIB_PATH)\wdmsec.lib</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <FilesToPackage Include="$(TargetPath)" />
+  </ItemGroup>
+  <ItemGroup>
+    <Inf Include="netuio.inf" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="netuio_dev.h" />
+    <ClInclude Include="netuio_drv.h" />
+    <ClInclude Include="netuio_interface.h" />
+    <ClInclude Include="netuio_queue.h" />
+    <ClInclude Include="resource.h" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="netuio_dev.c" />
+    <ClCompile Include="netuio_drv.c" />
+    <ClCompile Include="netuio_queue.c" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>
\ No newline at end of file
diff --git a/windows/netuio/netuio.vcxproj.filters b/windows/netuio/netuio.vcxproj.filters
new file mode 100644
index 000000000..49c1da15b
--- /dev/null
+++ b/windows/netuio/netuio.vcxproj.filters
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <Filter Include="Source Files">
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+    </Filter>
+    <Filter Include="Header Files">
+      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+      <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
+    </Filter>
+    <Filter Include="Resource Files">
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+    </Filter>
+    <Filter Include="Driver Files">
+      <UniqueIdentifier>{8E41214B-6785-4CFE-B992-037D68949A14}</UniqueIdentifier>
+      <Extensions>inf;inv;inx;mof;mc;</Extensions>
+    </Filter>
+  </ItemGroup>
+  <ItemGroup>
+    <Inf Include="netuio.inf">
+      <Filter>Driver Files</Filter>
+    </Inf>
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="netuio_interface.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="netuio_dev.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="netuio_drv.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="netuio_queue.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="resource.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="netuio_queue.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="netuio_dev.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="netuio_drv.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+  </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/windows/netuio/netuio_dev.c b/windows/netuio/netuio_dev.c
new file mode 100644
index 000000000..8f5e867f8
--- /dev/null
+++ b/windows/netuio/netuio_dev.c
@@ -0,0 +1,286 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Microsoft Corporation.
+ */
+
+#include <stdio.h>
+#include "netuio_drv.h"
+
+#include <wdmguid.h>
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text (PAGE, netuio_create_device)
+#pragma alloc_text (PAGE, netuio_map_hw_resources)
+#pragma alloc_text (PAGE, netuio_free_hw_resources)
+#endif
+
+static NTSTATUS
+get_pci_device_info(_In_ WDFOBJECT device)
+{
+    NTSTATUS status = STATUS_UNSUCCESSFUL;
+
+    PNETUIO_CONTEXT_DATA  ctx;
+    ctx = netuio_get_context_data(device);
+
+    if (!ctx)
+        return status;
+
+    ctx->wdf_device = device;  // Store for later use
+
+    // Obtain the BUS_INTERFACE_STANDARD interface from the Bus Driver
+    status = WdfFdoQueryForInterface(device, &GUID_BUS_INTERFACE_STANDARD,
+        (PINTERFACE)&ctx->bus_interface,
+        sizeof(BUS_INTERFACE_STANDARD), 1, NULL);
+    if (!NT_SUCCESS(status))
+        return status;
+
+    // Retrieve the B:D:F details of our device
+    PDEVICE_OBJECT pdo = NULL;
+    pdo = WdfDeviceWdmGetPhysicalDevice(device);
+    if (pdo) {
+        ULONG prop = 0, length = 0;
+        status = IoGetDeviceProperty(pdo, DevicePropertyBusNumber, sizeof(ULONG), (PVOID)&ctx->addr.bus_num, &length);
+        status = IoGetDeviceProperty(pdo, DevicePropertyAddress, sizeof(ULONG), (PVOID)&prop, &length);
+
+        if (NT_SUCCESS(status)) {
+            ctx->addr.func_num = prop & 0x0000FFFF;
+            ctx->addr.dev_num = ((prop >> 16) & 0x0000FFFF);
+        }
+    }
+
+    return status;
+}
+
+static NTSTATUS
+create_device_specific_symbolic_link(_In_ WDFOBJECT device)
+{
+    NTSTATUS status = STATUS_UNSUCCESSFUL;
+    UNICODE_STRING netuio_symbolic_link;
+
+    PNETUIO_CONTEXT_DATA  ctx;
+    ctx = netuio_get_context_data(device);
+
+    if (!ctx)
+        return status;
+
+    // Build symbolic link name as <netuio_symbolic_link>_BDF  (bus/device/func)
+    CHAR  symbolic_link[64] = { 0 };
+    sprintf_s(symbolic_link, sizeof(symbolic_link), "%s_%04d%02d%02d",
+        NETUIO_DEVICE_SYMBOLIC_LINK_ANSI, ctx->addr.bus_num,
+        ctx->addr.dev_num, ctx->addr.func_num);
+
+    ANSI_STRING ansi_symbolic_link;
+    RtlInitAnsiString(&ansi_symbolic_link, symbolic_link);
+
+    status = RtlAnsiStringToUnicodeString(&netuio_symbolic_link, &ansi_symbolic_link, TRUE);
+    if (!NT_SUCCESS(status))
+        return status;
+
+    status = WdfDeviceCreateSymbolicLink(device, &netuio_symbolic_link);
+
+    RtlFreeUnicodeString(&netuio_symbolic_link);
+
+    return status;
+}
+
+/*
+Routine Description:
+    Worker routine called to create a device and its software resources.
+
+Return Value:
+    NTSTATUS
+ */
+_Use_decl_annotations_
+NTSTATUS
+netuio_create_device(PWDFDEVICE_INIT DeviceInit)
+{
+    NTSTATUS status;
+    WDFDEVICE device = NULL;
+
+    WDF_OBJECT_ATTRIBUTES deviceAttributes;
+    WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;
+    WDF_FILEOBJECT_CONFIG fileConfig;
+    WDF_OBJECT_ATTRIBUTES fileAttributes;
+
+    PAGED_CODE();
+
+    // Register PnP power management callbacks
+    WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
+    pnpPowerCallbacks.EvtDevicePrepareHardware = netuio_evt_prepare_hw;
+    pnpPowerCallbacks.EvtDeviceReleaseHardware = netuio_evt_release_hw;
+    WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);
+
+    // Register callbacks for when a HANDLE is opened or closed.
+    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&fileAttributes, NETUIO_FILE_CONTEXT_DATA);
+    WDF_FILEOBJECT_CONFIG_INIT(&fileConfig, WDF_NO_EVENT_CALLBACK, WDF_NO_EVENT_CALLBACK, netuio_evt_file_cleanup);
+    WdfDeviceInitSetFileObjectConfig(DeviceInit, &fileConfig, &fileAttributes);
+
+    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, NETUIO_CONTEXT_DATA);
+    WdfDeviceInitSetIoInCallerContextCallback(DeviceInit, netuio_evt_IO_in_caller_context);
+
+    status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);
+
+    if (NT_SUCCESS(status)) {
+        // Create a device interface so that applications can find and talk to us.
+        status = WdfDeviceCreateDeviceInterface(device, &GUID_DEVINTERFACE_netUIO, NULL);
+    }
+
+    if (NT_SUCCESS(status)) {
+        // Retrieve and store PCI information
+        status = get_pci_device_info(device);
+    }
+
+    if (NT_SUCCESS(status)) {
+        // Create a symbolic link name for user-space access
+        status = create_device_specific_symbolic_link(device);
+    }
+
+    if (NT_SUCCESS(status)) {
+        // Initialize the I/O Package and any Queues
+        status = netuio_queue_initialize(device);
+    }
+
+    return status;
+}
+
+_Use_decl_annotations_
+NTSTATUS
+netuio_map_hw_resources(WDFDEVICE Device, WDFCMRESLIST Resources, WDFCMRESLIST ResourcesTranslated)
+{
+    UNREFERENCED_PARAMETER(Resources);
+
+    NTSTATUS status;
+
+    PNETUIO_CONTEXT_DATA  ctx;
+    ctx = netuio_get_context_data(Device);
+
+    if (!ctx) {
+        return STATUS_UNSUCCESSFUL;
+    }
+
+    PCI_COMMON_HEADER pci_config = {0};
+    ULONG bytes_returned;
+
+    // Read PCI configuration space
+    bytes_returned = ctx->bus_interface.GetBusData(
+        ctx->bus_interface.Context,
+        PCI_WHICHSPACE_CONFIG,
+        &pci_config,
+        0,
+        sizeof(pci_config));
+
+    if (bytes_returned != sizeof(pci_config)) {
+        status = STATUS_NOT_SUPPORTED;
+        goto end;
+    }
+
+    // Device type is implictly enforced by .inf
+    ASSERT(PCI_CONFIGURATION_TYPE(&pci_config) == PCI_DEVICE_TYPE);
+
+    PCM_PARTIAL_RESOURCE_DESCRIPTOR descriptor;
+    ULONG next_descriptor = 0;
+    ULONGLONG bar_addr = 0;
+    ULONG curr_bar = 0;
+    ULONG prev_bar = 0;
+
+    for (INT bar_index = 0; bar_index < PCI_MAX_BAR; bar_index++) {
+        prev_bar = curr_bar;
+        curr_bar = pci_config.u.type0.BaseAddresses[bar_index];
+        if (curr_bar == 0 || (prev_bar & PCI_TYPE_64BIT)) {
+            // Skip this bar
+            ctx->bar[bar_index].base_addr.QuadPart = 0;
+            ctx->bar[bar_index].size = 0;
+            ctx->bar[bar_index].virt_addr = 0;
+
+            ctx->dpdk_hw[bar_index].mdl = NULL;
+            ctx->dpdk_hw[bar_index].mem.size = 0;
+
+            continue;
+        }
+
+        // Find next CmResourceTypeMemory
+        do {
+            descriptor = WdfCmResourceListGetDescriptor(ResourcesTranslated, next_descriptor);
+            next_descriptor++;
+
+            if (descriptor == NULL) {
+                status = STATUS_DEVICE_CONFIGURATION_ERROR;
+                goto end;
+            }
+        } while (descriptor->Type != CmResourceTypeMemory);
+
+        // Assert that we have the correct descriptor
+        ASSERT((descriptor->Flags & CM_RESOURCE_MEMORY_BAR) != 0);
+
+        if (curr_bar & PCI_TYPE_64BIT) {
+            ASSERT(bar_index != PCI_TYPE0_ADDRESSES - 1);
+            bar_addr = ((ULONGLONG)pci_config.u.type0.BaseAddresses[bar_index + 1] << 32) | (curr_bar & PCI_ADDRESS_MEMORY_ADDRESS_MASK);
+        }
+        else
+        {
+            bar_addr = curr_bar & PCI_ADDRESS_MEMORY_ADDRESS_MASK;
+        }
+
+        ASSERT((ULONGLONG)descriptor->u.Memory.Start.QuadPart == bar_addr);
+
+        // Retrieve and map the BARs
+        ctx->bar[bar_index].base_addr.QuadPart = descriptor->u.Memory.Start.QuadPart;
+        ctx->bar[bar_index].size = descriptor->u.Memory.Length;
+        ctx->bar[bar_index].virt_addr = MmMapIoSpace(descriptor->u.Memory.Start,
+                                                     descriptor->u.Memory.Length,
+                                                     MmNonCached);
+        if (ctx->bar[bar_index].virt_addr == NULL) {
+            status = STATUS_INSUFFICIENT_RESOURCES;
+            goto end;
+        }
+
+        // Allocate an MDL for the device BAR, so we can map it to the user's process context later.
+        ctx->dpdk_hw[bar_index].mdl = IoAllocateMdl(ctx->bar[bar_index].virt_addr,
+                                                    (ULONG)ctx->bar[bar_index].size,
+                                                    FALSE,
+                                                    FALSE,
+                                                    NULL);
+        if (!ctx->dpdk_hw[bar_index].mdl) {
+            status = STATUS_INSUFFICIENT_RESOURCES;
+            goto end;
+        }
+
+        ctx->dpdk_hw[bar_index].mem.size = ctx->bar[bar_index].size;
+    } // for bar_index
+
+    status = STATUS_SUCCESS;
+
+end:
+    if (status != STATUS_SUCCESS) {
+        netuio_free_hw_resources(Device);
+    }
+
+    return status;
+}
+
+_Use_decl_annotations_
+VOID
+netuio_free_hw_resources(WDFDEVICE Device)
+{
+    PNETUIO_CONTEXT_DATA  ctx;
+    ctx = netuio_get_context_data(Device);
+
+    if (ctx) {
+        for (UINT8 bar_index = 0; bar_index < PCI_MAX_BAR; bar_index++) {
+
+            // Free the allocated MDLs
+            if (ctx->dpdk_hw[bar_index].mdl) {
+                IoFreeMdl(ctx->dpdk_hw[bar_index].mdl);
+            }
+
+            // Unmap all the BAR regions previously mapped
+            if (ctx->bar[bar_index].virt_addr) {
+                MmUnmapIoSpace(ctx->bar[bar_index].virt_addr, ctx->bar[bar_index].size);
+            }
+        }
+
+        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
new file mode 100644
index 000000000..9b97a4908
--- /dev/null
+++ b/windows/netuio/netuio_dev.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Microsoft Corporation.
+ */
+
+#ifndef NETUIO_DEV_H
+#define NETUIO_DEV_H
+
+#include "netuio_interface.h"
+
+struct pci_bar {
+    PHYSICAL_ADDRESS base_addr;
+    PVOID            virt_addr;
+    UINT64           size;
+};
+
+struct mem_map_region {
+    PMDL               mdl;    // MDL describing the memory region
+    struct mem_region  mem;    // Memory region details
+};
+
+// The device context performs the same job as a WDM device extension in the driver frameworks
+typedef struct _NETUIO_CONTEXT_DATA
+{
+    WDFDEVICE               wdf_device;             // WDF device handle to the FDO
+    BUS_INTERFACE_STANDARD  bus_interface;          // Bus interface for config space access
+    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
+} NETUIO_CONTEXT_DATA, *PNETUIO_CONTEXT_DATA;
+
+
+// This macro will generate an inline function called DeviceGetContext
+// which will be used to get a pointer to the device context memory in a type safe manner.
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(NETUIO_CONTEXT_DATA, netuio_get_context_data)
+
+typedef struct
+{
+    BOOLEAN bMapped;     // value is set to TRUE if the User-mode mapping was done for this file object.
+}  NETUIO_FILE_CONTEXT_DATA, * PNETUIO_FILE_CONTEXT_DATA;
+
+// This macro will generate an inline function which will be used to get a pointer to the
+// file object's context memory in a type safe manner.
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(NETUIO_FILE_CONTEXT_DATA, netuio_get_file_object_context_data)
+
+// Function to initialize the device and its callbacks
+NTSTATUS netuio_create_device(_Inout_ PWDFDEVICE_INIT DeviceInit);
+NTSTATUS netuio_map_hw_resources(_In_ WDFDEVICE Device, _In_ WDFCMRESLIST Resources, _In_ WDFCMRESLIST ResourcesTranslated);
+VOID netuio_free_hw_resources(_In_ WDFDEVICE Device);
+
+#endif // NETUIO_DEV_H
diff --git a/windows/netuio/netuio_drv.c b/windows/netuio/netuio_drv.c
new file mode 100644
index 000000000..ca529222c
--- /dev/null
+++ b/windows/netuio/netuio_drv.c
@@ -0,0 +1,158 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Microsoft Corporation.
+ */
+
+#include "netuio_drv.h"
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text (INIT, DriverEntry)
+#pragma alloc_text (PAGE, netuio_evt_device_add)
+#pragma alloc_text (PAGE, netuio_evt_driver_context_cleanup)
+#endif
+
+/*
+Routine Description:
+    DriverEntry initializes the driver and is the first routine called by the
+    system after the driver is loaded. DriverEntry specifies the other entry
+    points in the function driver, such as EvtDevice and DriverUnload.
+
+Return Value:
+    STATUS_SUCCESS if successful,
+    STATUS_UNSUCCESSFUL otherwise.
+ */
+NTSTATUS
+DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath)
+{
+    WDF_DRIVER_CONFIG config;
+    NTSTATUS status;
+    WDF_OBJECT_ATTRIBUTES attributes;
+
+    // Register a cleanup callback so that we can call WPP_CLEANUP when
+    // the framework driver object is deleted during driver unload.
+    WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+    attributes.EvtCleanupCallback = netuio_evt_driver_context_cleanup;
+
+    WDF_DRIVER_CONFIG_INIT(&config, netuio_evt_device_add);
+
+    status = WdfDriverCreate(DriverObject, RegistryPath,
+                             &attributes, &config,
+                             WDF_NO_HANDLE);
+
+    if (!NT_SUCCESS(status)) {
+        return status;
+    }
+
+    return status;
+}
+
+/*
+Routine Description:
+    netuio_evt_device_add is called by the framework in response to AddDevice
+    call from the PnP manager. We create and initialize a device object to
+    represent a new instance of the device.
+
+Return Value:
+    NTSTATUS
+ */
+_Use_decl_annotations_
+NTSTATUS
+netuio_evt_device_add(WDFDRIVER Driver, PWDFDEVICE_INIT DeviceInit)
+{
+    UNREFERENCED_PARAMETER(Driver);
+    return netuio_create_device(DeviceInit);
+}
+
+/*
+Routine Description :
+    Maps HW resources and retrieves the PCI BAR address(es) of the device
+
+Return Value :
+    STATUS_SUCCESS is successful.
+    STATUS_<ERROR> otherwise
+-*/
+_Use_decl_annotations_
+NTSTATUS
+netuio_evt_prepare_hw(WDFDEVICE Device, WDFCMRESLIST Resources, WDFCMRESLIST ResourcesTranslated)
+{
+    NTSTATUS status;
+
+    status = netuio_map_hw_resources(Device, Resources, ResourcesTranslated);
+
+    if (NT_SUCCESS(status)) {
+        PNETUIO_CONTEXT_DATA  ctx;
+        ctx = netuio_get_context_data(Device);
+        if (ctx) {
+            DbgPrintEx(DPFLTR_IHVNETWORK_ID, DPFLTR_NETUIO_INFO_LEVEL, "netUIO Driver loaded...on device (B:D:F) %04d:%02d:%02d\n",
+                                             ctx->addr.bus_num, ctx->addr.dev_num, ctx->addr.func_num);
+        }
+    }
+    return status;
+}
+
+/*
+Routine Description :
+    Releases the resource mapped by netuio_evt_prepare_hw
+
+Return Value :
+    STATUS_SUCCESS always.
+-*/
+_Use_decl_annotations_
+NTSTATUS
+netuio_evt_release_hw(WDFDEVICE Device, WDFCMRESLIST ResourcesTranslated)
+{
+    UNREFERENCED_PARAMETER(ResourcesTranslated);
+
+    netuio_free_hw_resources(Device);
+
+    return STATUS_SUCCESS;
+}
+
+/*
+Routine Description:
+    Free all the resources allocated in DriverEntry.
+
+Return Value:
+    None
+-*/
+_Use_decl_annotations_
+VOID
+netuio_evt_driver_context_cleanup(WDFOBJECT DriverObject)
+{
+    UNREFERENCED_PARAMETER(DriverObject);
+    DbgPrintEx(DPFLTR_IHVNETWORK_ID, DPFLTR_NETUIO_INFO_LEVEL, "netUIO Driver unloaded.\n");
+    PAGED_CODE();
+}
+
+/*
+Routine Description :
+    EVT_WDF_FILE_CLEANUP callback, called when user process handle is closed.
+
+    Undoes IOCTL_NETUIO_MAP_HW_INTO_USERMODE.
+
+Return value :
+    None
+-*/
+_Use_decl_annotations_
+VOID
+netuio_evt_file_cleanup(WDFFILEOBJECT FileObject)
+{
+    PNETUIO_FILE_CONTEXT_DATA file_ctx;
+
+    file_ctx = netuio_get_file_object_context_data(FileObject);
+
+    if ((file_ctx != NULL) &&
+        (file_ctx->bMapped == TRUE))
+    {
+        WDFDEVICE device;
+        PNETUIO_CONTEXT_DATA ctx;
+
+        device = WdfFileObjectGetDevice(FileObject);
+        ctx = netuio_get_context_data(device);
+
+        if (ctx)
+        {
+            netuio_unmap_address_from_user_process(ctx);
+        }
+        file_ctx->bMapped = FALSE;
+    }
+}
diff --git a/windows/netuio/netuio_drv.h b/windows/netuio/netuio_drv.h
new file mode 100644
index 000000000..5bc0f90f9
--- /dev/null
+++ b/windows/netuio/netuio_drv.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Microsoft Corporation.
+ */
+
+#ifndef NETUIO_DRV_H
+#define NETUIO_DRV_H
+
+#define INITGUID
+
+#include <ntddk.h>
+#include <wdf.h>
+
+#include "netuio_dev.h"
+#include "netuio_queue.h"
+
+// Print output constants
+#define DPFLTR_NETUIO_INFO_LEVEL   35
+
+// WDFDRIVER Events
+DRIVER_INITIALIZE DriverEntry;
+EVT_WDF_DRIVER_DEVICE_ADD       netuio_evt_device_add;
+EVT_WDF_OBJECT_CONTEXT_CLEANUP  netuio_evt_driver_context_cleanup;
+EVT_WDF_DEVICE_PREPARE_HARDWARE netuio_evt_prepare_hw;
+EVT_WDF_DEVICE_RELEASE_HARDWARE netuio_evt_release_hw;
+EVT_WDF_FILE_CLOSE              netuio_evt_file_cleanup;
+
+#endif // NETUIO_DRV_H
diff --git a/windows/netuio/netuio_interface.h b/windows/netuio/netuio_interface.h
new file mode 100644
index 000000000..6fc75ad8e
--- /dev/null
+++ b/windows/netuio/netuio_interface.h
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Microsoft Corporation.
+ */
+
+#ifndef NETUIO_INTERFACE_H
+#define NETUIO_INTERFACE_H
+
+// All structures in this file are packed on an 8B boundary. 
+#pragma pack(push)
+#pragma pack(8)
+
+// Define an Interface Guid so that any app can find the device and talk to it.
+DEFINE_GUID (GUID_DEVINTERFACE_netUIO, 0x08336f60,0x0679,0x4c6c,0x85,0xd2,0xae,0x7c,0xed,0x65,0xff,0xf7); // {08336f60-0679-4c6c-85d2-ae7ced65fff7}
+
+// Device name definitions
+#define NETUIO_DEVICE_SYMBOLIC_LINK_ANSI    "\\DosDevices\\netuio"
+
+// netUIO driver symbolic name (prefix)
+#define NETUIO_DRIVER_NAME  _T("netuio")
+
+// IOCTL code definitions
+#define IOCTL_NETUIO_MAP_HW_INTO_USERMODE CTL_CODE(FILE_DEVICE_NETWORK, 51, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
+#define IOCTL_NETUIO_PCI_CONFIG_IO        CTL_CODE(FILE_DEVICE_NETWORK, 52, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
+
+struct mem_region {
+    UINT64           size;       // memory region size
+    LARGE_INTEGER    phys_addr;  // physical address of the memory region
+    PVOID            virt_addr;  // virtual address of the memory region
+    PVOID            user_mapped_virt_addr;  // virtual address of the region mapped into user process context
+};
+
+struct dev_addr {
+    ULONG   bus_num;
+    USHORT  dev_num;
+    USHORT  func_num;
+};
+
+enum pci_io {
+    PCI_IO_READ = 0,
+    PCI_IO_WRITE = 1
+};
+
+#define PCI_MAX_BAR 6
+
+struct device_info
+{
+    struct mem_region   hw[PCI_MAX_BAR];
+    USHORT              reserved;
+};
+
+struct dpdk_pci_config_io
+{
+    UINT32              offset;
+    UINT8               op;
+    UINT32              access_size; // 1, 2, 4, or 8 bytes
+
+    union dpdk_pci_config_io_data {
+        UINT8			u8;
+        UINT16			u16;
+        UINT32			u32;
+        UINT64			u64;
+    } data;
+};
+
+#pragma pack(pop)
+
+#endif // NETUIO_INTERFACE_H
diff --git a/windows/netuio/netuio_queue.c b/windows/netuio/netuio_queue.c
new file mode 100644
index 000000000..cea94e630
--- /dev/null
+++ b/windows/netuio/netuio_queue.c
@@ -0,0 +1,333 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Microsoft Corporation.
+ */
+
+#include "netuio_drv.h"
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text (PAGE, netuio_queue_initialize)
+#endif
+
+static
+BOOLEAN
+netuio_get_usermode_mapping_flag(WDFREQUEST Request)
+{
+    WDFFILEOBJECT file_object;
+
+    file_object = WdfRequestGetFileObject(Request);
+    if (file_object == NULL)
+    {
+        goto end;
+    }
+
+    PNETUIO_FILE_CONTEXT_DATA file_ctx;
+
+    file_ctx = netuio_get_file_object_context_data(file_object);
+
+    if (file_ctx && file_ctx->bMapped)
+    {
+        return TRUE;
+    }
+
+end:
+    return FALSE;
+}
+static
+VOID
+netuio_set_usermode_mapping_flag(WDFREQUEST Request)
+{
+    WDFFILEOBJECT file_object;
+
+    file_object = WdfRequestGetFileObject(Request);
+    if (file_object == NULL)
+    {
+        return;
+    }
+
+    PNETUIO_FILE_CONTEXT_DATA file_ctx;
+
+    file_ctx = netuio_get_file_object_context_data(file_object);
+
+    if (file_ctx != NULL)
+    {
+        file_ctx->bMapped = TRUE;
+    }
+}
+
+static void
+netuio_handle_get_hw_data_request(_In_ PNETUIO_CONTEXT_DATA ctx,
+                                  _In_ PVOID outputBuf, _In_ size_t outputBufSize)
+{
+    ASSERT(outputBufSize == sizeof(struct device_info));
+
+    struct device_info *dpdk_pvt_info = (struct device_info *)outputBuf;
+    RtlZeroMemory(dpdk_pvt_info, outputBufSize);
+
+    for (ULONG idx = 0; idx < PCI_MAX_BAR; idx++) {
+        dpdk_pvt_info->hw[idx].phys_addr.QuadPart = ctx->bar[idx].base_addr.QuadPart;
+        dpdk_pvt_info->hw[idx].user_mapped_virt_addr = ctx->dpdk_hw[idx].mem.user_mapped_virt_addr;
+        dpdk_pvt_info->hw[idx].size = ctx->bar[idx].size;
+    }
+}
+
+/*
+Routine Description:
+    Maps address ranges into the usermode process's address space.  The following
+    ranges are mapped:
+
+        * Any PCI BARs that our device was assigned
+        * The scratch buffer of contiguous pages
+
+Return Value:
+    NTSTATUS
+*/
+static NTSTATUS
+netuio_map_address_into_user_process(_In_ PNETUIO_CONTEXT_DATA ctx, WDFREQUEST Request)
+{
+    NTSTATUS status = STATUS_SUCCESS;
+
+    if (netuio_get_usermode_mapping_flag(Request))
+    {
+        goto end;
+    }
+
+    // Map any device BAR(s) to the user's process context
+    for (INT idx = 0; idx < PCI_MAX_BAR; idx++) {
+        if (ctx->dpdk_hw[idx].mdl == NULL) {
+            continue;
+        }
+
+        MmBuildMdlForNonPagedPool(ctx->dpdk_hw[idx].mdl);
+        __try {
+            ctx->dpdk_hw[idx].mem.user_mapped_virt_addr =
+                MmMapLockedPagesSpecifyCache(ctx->dpdk_hw[idx].mdl, UserMode,
+                                             MmCached, NULL, FALSE, NormalPagePriority);
+
+            if (ctx->dpdk_hw[idx].mem.user_mapped_virt_addr == NULL) {
+                status = STATUS_INSUFFICIENT_RESOURCES;
+                goto end;
+            }
+        }
+        __except (EXCEPTION_EXECUTE_HANDLER) {
+            status = GetExceptionCode();
+            goto end;
+        }
+    }
+
+end:
+    if (status != STATUS_SUCCESS) {
+        netuio_unmap_address_from_user_process(ctx);
+    }
+
+    return status;
+}
+
+/*
+Routine Description:
+    Unmaps all address ranges from the usermode process address space.
+    MUST be called in the context of the same process which created
+    the mapping.
+
+Return Value:
+    None
+ */
+_Use_decl_annotations_
+VOID
+netuio_unmap_address_from_user_process(PNETUIO_CONTEXT_DATA ctx)
+{
+    for (INT idx = 0; idx < PCI_MAX_BAR; idx++) {
+        if (ctx->dpdk_hw[idx].mem.user_mapped_virt_addr != NULL) {
+            MmUnmapLockedPages(ctx->dpdk_hw[idx].mem.user_mapped_virt_addr,
+                               ctx->dpdk_hw[idx].mdl);
+
+            ctx->dpdk_hw[idx].mem.user_mapped_virt_addr = NULL;
+        }
+    }
+}
+
+/*
+Routine Description:
+    The I/O dispatch callbacks for the frameworks device object are configured here.
+    A single default I/O Queue is configured for parallel request processing, and a
+    driver context memory allocation is created to hold our structure QUEUE_CONTEXT.
+
+Return Value:
+    None
+ */
+_Use_decl_annotations_
+NTSTATUS
+netuio_queue_initialize(WDFDEVICE Device)
+{
+    WDFQUEUE queue;
+    NTSTATUS status;
+    WDF_IO_QUEUE_CONFIG    queueConfig;
+
+    PAGED_CODE();
+
+    // Configure a default queue so that requests that are not
+    // configure-fowarded using WdfDeviceConfigureRequestDispatching to goto
+    // other queues get dispatched here.
+    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig, WdfIoQueueDispatchParallel);
+
+    queueConfig.EvtIoDeviceControl = netuio_evt_IO_device_control;
+
+    status = WdfIoQueueCreate(Device,
+                              &queueConfig,
+                              WDF_NO_OBJECT_ATTRIBUTES,
+                              &queue);
+
+    if( !NT_SUCCESS(status) ) {
+        return status;
+    }
+
+    return status;
+}
+
+/*
+Routine Description:
+    This routine is invoked to preprocess an I/O request before being placed into a queue.
+    It is guaranteed that it executes in the context of the process that generated the request.
+
+Return Value:
+    None
+ */
+_Use_decl_annotations_
+VOID
+netuio_evt_IO_in_caller_context(WDFDEVICE  Device,
+                                WDFREQUEST Request)
+{
+    WDF_REQUEST_PARAMETERS params = { 0 };
+    NTSTATUS status = STATUS_SUCCESS;
+    PVOID    output_buf = NULL;
+    size_t   output_buf_size;
+    size_t  bytes_returned = 0;
+    PNETUIO_CONTEXT_DATA  ctx = NULL;
+
+    ctx = netuio_get_context_data(Device);
+
+    WDF_REQUEST_PARAMETERS_INIT(&params);
+    WdfRequestGetParameters(Request, &params);
+
+    // We only need to be in the context of the process that initiated the request
+    //when we need to map memory to userspace. Otherwise, send the request back to framework.
+    if (!((params.Type == WdfRequestTypeDeviceControl) &&
+        (params.Parameters.DeviceIoControl.IoControlCode == IOCTL_NETUIO_MAP_HW_INTO_USERMODE)))
+    {
+        status = WdfDeviceEnqueueRequest(Device, Request);
+
+        if (!NT_SUCCESS(status))
+        {
+            WdfRequestCompleteWithInformation(Request, status, bytes_returned);
+        }
+        return;
+    }
+
+    // Return relevant data to the caller
+    status = WdfRequestRetrieveOutputBuffer(Request, sizeof(struct device_info), &output_buf, &output_buf_size);
+    if (!NT_SUCCESS(status)) {
+        status = STATUS_INVALID_BUFFER_SIZE;
+        goto end;
+    }
+
+    status = netuio_map_address_into_user_process(ctx, Request);
+    if (status != STATUS_SUCCESS) {
+        goto end;
+    }
+
+    netuio_set_usermode_mapping_flag(Request);
+
+    netuio_handle_get_hw_data_request(ctx, output_buf, output_buf_size);
+    bytes_returned = output_buf_size;
+
+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)
+{
+    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);
+
+    PNETUIO_CONTEXT_DATA  ctx;
+    ctx = netuio_get_context_data(device);
+
+    if (IoControlCode != IOCTL_NETUIO_PCI_CONFIG_IO)
+    {
+        status = STATUS_INVALID_DEVICE_REQUEST;
+        goto end;
+    }
+
+    // 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);
+    if (!NT_SUCCESS(status)) {
+        status = STATUS_INVALID_BUFFER_SIZE;
+        goto end;
+    }
+
+    struct dpdk_pci_config_io *dpdk_pci_io_input = (struct dpdk_pci_config_io *)input_buf;
+
+    if (dpdk_pci_io_input->access_size != 1 &&
+        dpdk_pci_io_input->access_size != 2 &&
+        dpdk_pci_io_input->access_size != 4 &&
+        dpdk_pci_io_input->access_size != 8) {
+        status = STATUS_INVALID_PARAMETER;
+        goto end;
+    }
+
+    // Retrieve output buffer
+    status = WdfRequestRetrieveOutputBuffer(Request, sizeof(UINT64), &output_buf, &output_buf_size);
+    if (!NT_SUCCESS(status)) {
+        status = STATUS_INVALID_BUFFER_SIZE;
+        goto end;
+    }
+    ASSERT(output_buf_size == OutputBufferLength);
+
+    if (dpdk_pci_io_input->op == PCI_IO_READ) {
+        *(UINT64 *)output_buf = 0;
+        bytes_returned = ctx->bus_interface.GetBusData(
+            ctx->bus_interface.Context,
+            PCI_WHICHSPACE_CONFIG,
+            output_buf,
+            dpdk_pci_io_input->offset,
+            dpdk_pci_io_input->access_size);
+    }
+    else if (dpdk_pci_io_input->op == PCI_IO_WRITE) {
+        // returns bytes written
+        bytes_returned = ctx->bus_interface.SetBusData(
+            ctx->bus_interface.Context,
+            PCI_WHICHSPACE_CONFIG,
+            (PVOID)&dpdk_pci_io_input->data,
+            dpdk_pci_io_input->offset,
+            dpdk_pci_io_input->access_size);
+    }
+    else {
+        status = STATUS_INVALID_PARAMETER;
+        goto end;
+    }
+
+end:
+    WdfRequestCompleteWithInformation(Request, status, bytes_returned);
+
+    return;
+}
+
diff --git a/windows/netuio/netuio_queue.h b/windows/netuio/netuio_queue.h
new file mode 100644
index 000000000..42f75be38
--- /dev/null
+++ b/windows/netuio/netuio_queue.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Microsoft Corporation.
+ */
+
+#ifndef NETUIO_QUEUE_H
+#define NETUIO_QUEUE_H
+
+VOID
+netuio_unmap_address_from_user_process(_In_ PNETUIO_CONTEXT_DATA netuio_contextdata);
+
+NTSTATUS
+netuio_queue_initialize(_In_ WDFDEVICE hDevice);
+
+// Events from the IoQueue object
+EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL netuio_evt_IO_device_control;
+
+EVT_WDF_IO_IN_CALLER_CONTEXT netuio_evt_IO_in_caller_context;
+
+#endif // NETUIO_QUEUE_H
-- 
2.23.0.vfs.1.1.63.g5a5ad7f


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [dpdk-dev] [PATCH v2] windows/netuio: add Windows NetUIO kernel driver
  2020-08-24 20:53 ` Dmitry Kozlyuk
@ 2020-09-09 18:53   ` Narcisa Ana Maria Vasile
  2020-09-13 21:39     ` Dmitry Kozlyuk
  0 siblings, 1 reply; 16+ messages in thread
From: Narcisa Ana Maria Vasile @ 2020-09-09 18:53 UTC (permalink / raw)
  To: Dmitry Kozlyuk
  Cc: dev, thomas, ocardona, haramakr, pallavi.kadam, ranjit.menon,
	dmitrym, Narcisa Vasile, Harini Ramakrishnan

On Mon, Aug 24, 2020 at 11:53:44PM +0300, Dmitry Kozlyuk wrote:
> On Thu, 20 Aug 2020 15:23:55 -0700, Narcisa Ana Maria Vasile wrote:
> > From: Narcisa Vasile <navasile@microsoft.com>
> > 
> > The Windows NetUIO kernel driver allows the DPDK userspace
> > application to directly access the hardware.
> > 
> > Cc: Harini Ramakrishnan <Harini.Ramakrishnan@microsoft.com>
> > Cc: Omar Cardona <ocardona@microsoft.com>
> > Signed-off-by: Narcisa Vasile <navasile@microsoft.com>
> > ---
> 
> Major questions:
> 
> 1. Does NetUIO still need to allocate and map a contiguous memory segment
> now, when DPDK has user-mode memory management?
> 
> 2. IOCTLs require to specify PCI address on each call. This is very
> inconvenient for DPDK consumers and also seems to serve no purpose.
> 
> 3. There is a need to document driver's design, preferably in commit message,
> specifically:
> 
> 3.1) DMA remapping capability in INF (AFAIK, vendors are notified);
> 3.2) manual BAR probing instead of using resource lists;
> 3.3) reason to use EvtIoInCallerContext and IO queues;
> 3.4) IOCTL format.
> 
> Also, I agree with everything Ranjit has noted already.
> 
> General suggestions to cleanup the code a bit. We can do it later if you
> wish. This also brings up the question, which code style should Windows
> kernel code for DPDK follow (off-topic for now).
> 
> * Remove boilerplate code and comments generated by VS wizard.
> * Place `_Use_decl_annotations` on definitions to make them simpler.
> * Limit line length, try using shorted variable names (e.g.
>   "netuio_contextdata" may be "context" or "ctx" with no loss).
> 

Thank you for you thorough review!

> More specific comments inline.
> 
> [snip]
> > +[Strings]
> > +SPSVCINST_ASSOCSERVICE= 0x00000002
> > +Intel = "Intel"
> > +Broadcom = "Broadcom Corporation"
> 
> IHVs are supposed to add this gradually.
> 

Thanks! I've modified the inf to remove all the vendor specific strings
but left the sections to avoid errors when generating the binary.

> > +ClassName = "Windows UIO"
> > +DiskName = "DPDK netUIO Installation Disk"
> > +netuio.DeviceDesc = "netuio Device"
> > +netuio.SVCDESC = "netuio Service"
> > +
> > +    // Build symbolic link name as <netuio_symbolic_link>_BDF  (bus/device/func)
> > +    CHAR  symbolic_link[64] = { 0 };
> > +    sprintf_s(symbolic_link, sizeof(symbolic_link), "%s_%04d%02d%02d",
> > +                            NETUIO_DEVICE_SYMBOLIC_LINK_ANSI, netuio_contextdata->addr.bus_num,
> > +                            netuio_contextdata->addr.dev_num, netuio_contextdata->addr.func_num);
> > +
> > +    ANSI_STRING ansi_symbolic_link;
> > +    RtlInitAnsiString(&ansi_symbolic_link, symbolic_link);
> > +
> > +    status = RtlAnsiStringToUnicodeString(&netuio_symbolic_link, &ansi_symbolic_link, TRUE);
> > +    if (!NT_SUCCESS(status))
> > +        return status;
> 
> Why not use Unicode directly?
> 
It looks like either way, a cast will be needed (for example, if I use wchar for symbolic_link and then
RtlInitUnicodeString()). I've left it as is, but let me know if there's an elegant solution that I didn't see.

> > +
> > +    status = WdfDeviceCreateSymbolicLink(device, &netuio_symbolic_link);
> > +
> > +    RtlFreeUnicodeString(&netuio_symbolic_link);
> > +
> > +    return status;

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [dpdk-dev] [PATCH v2] windows/netuio: add Windows NetUIO kernel driver
  2020-08-21  1:32 ` Ranjit Menon
@ 2020-09-09 18:58   ` Narcisa Ana Maria Vasile
  0 siblings, 0 replies; 16+ messages in thread
From: Narcisa Ana Maria Vasile @ 2020-09-09 18:58 UTC (permalink / raw)
  To: Ranjit Menon
  Cc: dev, thomas, ocardona, haramakr, pallavi.kadam, dmitry.kozliuk,
	dmitrym, Narcisa Vasile, Harini Ramakrishnan

On Thu, Aug 20, 2020 at 06:32:02PM -0700, Ranjit Menon wrote:
> Hi, Naty
> 
> On 8/20/2020 3:23 PM, Narcisa Ana Maria Vasile wrote:
> >From: Narcisa Vasile <navasile@microsoft.com>
> >
> >The Windows NetUIO kernel driver allows the DPDK userspace
> >application to directly access the hardware.
> >
> >Cc: Harini Ramakrishnan <Harini.Ramakrishnan@microsoft.com>
> >Cc: Omar Cardona <ocardona@microsoft.com>
> >Signed-off-by: Narcisa Vasile <navasile@microsoft.com>
> >---
> >
> I haven't reviewed the code yet, but a few general suggestions and
> observations:
> 
> 1. This code is going into the dpdk-kmods repo, so we don't need the
> four-level deep directory path for the source.
> 
> 2. The code can simply be in the windows/netuio directory (don't
> need 'kernel')
> 
> 3. The 'virt2phys' module that is already in the repo puts the
> build-related files (project files, solution files etc) in the same
> folder as the source files. We should do the same here. We don't
> need the mk/exec-env subfolder for these files. They can be in the
> same directory as the source.
> 
> 4. Remove the .vcxproj.user file from the patch. It is not required.
> 
> 5. The README should be renamed to simply README.rst and it can also
> be in this windows/netuio folder
> 
> 6. The existing README.rst in the windows folder contains
> information on how to build and load kernel modules in Windows. This
> new README can reference the root-level README, if required. (Also
> some of the information in this new README is out-of-date. I'll
> review that separately)
> 
> 
> ranjit m.
> 
Thank you, Ranjit! I've cleaned it up and sent v3.

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [dpdk-dev] [PATCH v3] windows/netuio: add Windows NetUIO kernel driver
  2020-09-09 18:41 ` [dpdk-dev] [PATCH v3] " Narcisa Ana Maria Vasile
@ 2020-09-13 21:39   ` Dmitry Kozlyuk
  2020-09-19  2:52   ` [dpdk-dev] [PATCH v4] " Narcisa Ana Maria Vasile
  1 sibling, 0 replies; 16+ messages in thread
From: Dmitry Kozlyuk @ 2020-09-13 21:39 UTC (permalink / raw)
  To: Narcisa Ana Maria Vasile
  Cc: dev, thomas, ocardona, haramakr, pallavi.kadam, ranjit.menon,
	dmitrym, Narcisa Vasile, Harini Ramakrishnan

On Wed,  9 Sep 2020 11:41:23 -0700, Narcisa Ana Maria Vasile wrote:
> From: Narcisa Vasile <navasile@microsoft.com>

Thanks for the hard work! A few comments inline worth checking, but this
version improved much anyway.

> The Windows netuio kernel driver provides the DPDK userspace application
> with direct access to hardware, by mapping the HW registers in userspace
> and allowing read/write operations from/to the device
> configuration space.
> 
> Two IOCTLs are defined by the netuio interface:
>   * IOCTL_NETUIO_MAP_HW_INTO_USERMODE
>       - used for mapping the device registers into userspace
> 
>       Description of output:
>       -	the physical address, virtual address and the size of the
>         memory region where the BARs were mapped.
> 
>   * IOCTL_NETUIO_PCI_CONFIG_IO
>       - used to read/write from/into the device configuration space
> 
>       Description of input:
>       -	the operation type (read/write)
>       -	the offset into the device data where the operation begins
>       -	the length of data to read/write.
>       -	in case of a write operation, the data to be written to
>         the device configuration space.
>       Description of output:
>       -	in case of a read operation, the output buffer is filled
>         with the data read from the configuration space.

Detailed parameter descriptions would better suit netuio_interface.h, where
the IOCTLs are defined. Short ones are useful here, though.

>  windows/netuio/README.rst             |  58 +++++

Nit: "git am" complains about spaces at end of a few lines in this file.

[snip]
> +static NTSTATUS
> +get_pci_device_info(_In_ WDFOBJECT device)
> +{
[snip]
> +    // Retrieve the B:D:F details of our device
> +    PDEVICE_OBJECT pdo = NULL;
> +    pdo = WdfDeviceWdmGetPhysicalDevice(device);
> +    if (pdo) {
> +        ULONG prop = 0, length = 0;
> +        status = IoGetDeviceProperty(pdo, DevicePropertyBusNumber, sizeof(ULONG), (PVOID)&ctx->addr.bus_num, &length);

Status is not checked and is immediately overwritten.

> +        status = IoGetDeviceProperty(pdo, DevicePropertyAddress, sizeof(ULONG), (PVOID)&prop, &length);
> +
> +        if (NT_SUCCESS(status)) {
> +            ctx->addr.func_num = prop & 0x0000FFFF;
> +            ctx->addr.dev_num = ((prop >> 16) & 0x0000FFFF);
> +        }
> +    }
> +
> +    return status;
> +}
> +
> +static NTSTATUS
> +create_device_specific_symbolic_link(_In_ WDFOBJECT device)
> +{
> +    NTSTATUS status = STATUS_UNSUCCESSFUL;
> +    UNICODE_STRING netuio_symbolic_link;
> +
> +    PNETUIO_CONTEXT_DATA  ctx;
> +    ctx = netuio_get_context_data(device);
> +
> +    if (!ctx)
> +        return status;

Can "ctx" really be NULL? Same applies to file object context. If I
get WdfObjectGetTypedContext() description right, it cannot, so you could
save a lot of checks.

> +
> +    // Build symbolic link name as <netuio_symbolic_link>_BDF  (bus/device/func)
> +    CHAR  symbolic_link[64] = { 0 };
> +    sprintf_s(symbolic_link, sizeof(symbolic_link), "%s_%04d%02d%02d",
> +        NETUIO_DEVICE_SYMBOLIC_LINK_ANSI, ctx->addr.bus_num,
> +        ctx->addr.dev_num, ctx->addr.func_num);

You probably want to use %x, and not %d for PCI addresses, because decimal
device numbers may not fit 2 digits and it will be hard to recognize. The
other place is DbgPrintEx() below. No strong opinion, however, because Device
Manager seems to use decimal.

> +
> +    ANSI_STRING ansi_symbolic_link;
> +    RtlInitAnsiString(&ansi_symbolic_link, symbolic_link);
> +
> +    status = RtlAnsiStringToUnicodeString(&netuio_symbolic_link, &ansi_symbolic_link, TRUE);
> +    if (!NT_SUCCESS(status))
> +        return status;
> +
> +    status = WdfDeviceCreateSymbolicLink(device, &netuio_symbolic_link);
> +
> +    RtlFreeUnicodeString(&netuio_symbolic_link);
> +
> +    return status;
> +}

[snip]
> +
> +_Use_decl_annotations_
> +NTSTATUS
> +netuio_map_hw_resources(WDFDEVICE Device, WDFCMRESLIST Resources, WDFCMRESLIST ResourcesTranslated)
> +{

Could you please document the solution here? Something along these lines:

	ResourcesTranslated report MMIO BARs in the correct order, but their
	indices differ from physical ones. Traverse PCI configuration
	manually to maintain physical index, searching for the next MMIO
	resource each time.

When I first implemented a netuio-like driver, I fell into this trap with
indices. Worse, I did work sometimes, e.g. with virtio-pci (legacy),that's
why I consider this non-obvious.

[snip]
> +    for (INT bar_index = 0; bar_index < PCI_MAX_BAR; bar_index++) {
> +        prev_bar = curr_bar;
> +        curr_bar = pci_config.u.type0.BaseAddresses[bar_index];
> +        if (curr_bar == 0 || (prev_bar & PCI_TYPE_64BIT)) {
> +            // Skip this bar
> +            ctx->bar[bar_index].base_addr.QuadPart = 0;
> +            ctx->bar[bar_index].size = 0;
> +            ctx->bar[bar_index].virt_addr = 0;
> +
> +            ctx->dpdk_hw[bar_index].mdl = NULL;
> +            ctx->dpdk_hw[bar_index].mem.size = 0;
> +
> +            continue;
> +        }

Maybe RtlZeroMmeory() the whole array beforehand and just continue here? This
would protect from forgetting to clear new fields, should they be added.

> +
> +        // Find next CmResourceTypeMemory
> +        do {
> +            descriptor = WdfCmResourceListGetDescriptor(ResourcesTranslated, next_descriptor);
> +            next_descriptor++;
> +
> +            if (descriptor == NULL) {
> +                status = STATUS_DEVICE_CONFIGURATION_ERROR;
> +                goto end;
> +            }
> +        } while (descriptor->Type != CmResourceTypeMemory);
> +
> +        // Assert that we have the correct descriptor
> +        ASSERT((descriptor->Flags & CM_RESOURCE_MEMORY_BAR) != 0);

Shouldn't this assert be a check? Or incorporated in loop condition maybe.

> +
> +        if (curr_bar & PCI_TYPE_64BIT) {
> +            ASSERT(bar_index != PCI_TYPE0_ADDRESSES - 1);
> +            bar_addr = ((ULONGLONG)pci_config.u.type0.BaseAddresses[bar_index + 1] << 32) | (curr_bar & PCI_ADDRESS_MEMORY_ADDRESS_MASK);
> +        }
> +        else
> +        {
> +            bar_addr = curr_bar & PCI_ADDRESS_MEMORY_ADDRESS_MASK;
> +        }
> +
> +        ASSERT((ULONGLONG)descriptor->u.Memory.Start.QuadPart == bar_addr);

Are these calculations really needed if you end up using address from the
resource descriptor?

> +
> +/*
> +Routine Description:
> +    Free all the resources allocated in DriverEntry.
> +
> +Return Value:
> +    None
> +-*/
> +_Use_decl_annotations_
> +VOID
> +netuio_evt_driver_context_cleanup(WDFOBJECT DriverObject)
> +{
> +    UNREFERENCED_PARAMETER(DriverObject);
> +    DbgPrintEx(DPFLTR_IHVNETWORK_ID, DPFLTR_NETUIO_INFO_LEVEL, "netUIO Driver unloaded.\n");
> +    PAGED_CODE();
> +}

The comment is misleading, no resources are freed here. You could've done
without this function, driver loading/unloading is logged anyway.
Also, PAGED_CODE() logically must come before any IRQL-constrained calls.

[snip]

> +#endif // NETUIO_DRV_H
> diff --git a/windows/netuio/netuio_interface.h b/windows/netuio/netuio_interface.h
> new file mode 100644
> index 000000000..6fc75ad8e
> --- /dev/null
> +++ b/windows/netuio/netuio_interface.h
> @@ -0,0 +1,67 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2020 Microsoft Corporation.
> + */
> +
> +#ifndef NETUIO_INTERFACE_H
> +#define NETUIO_INTERFACE_H
> +
> +// All structures in this file are packed on an 8B boundary. 
> +#pragma pack(push)
> +#pragma pack(8)
> +
> +// Define an Interface Guid so that any app can find the device and talk to it.
> +DEFINE_GUID (GUID_DEVINTERFACE_netUIO, 0x08336f60,0x0679,0x4c6c,0x85,0xd2,0xae,0x7c,0xed,0x65,0xff,0xf7); // {08336f60-0679-4c6c-85d2-ae7ced65fff7}
> +
> +// Device name definitions
> +#define NETUIO_DEVICE_SYMBOLIC_LINK_ANSI    "\\DosDevices\\netuio"
> +
> +// netUIO driver symbolic name (prefix)
> +#define NETUIO_DRIVER_NAME  _T("netuio")

This string is unused and is not needed for interface.

> +
> +// IOCTL code definitions
> +#define IOCTL_NETUIO_MAP_HW_INTO_USERMODE CTL_CODE(FILE_DEVICE_NETWORK, 51, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
> +#define IOCTL_NETUIO_PCI_CONFIG_IO        CTL_CODE(FILE_DEVICE_NETWORK, 52, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
> +
> +struct mem_region {
> +    UINT64           size;       // memory region size
> +    LARGE_INTEGER    phys_addr;  // physical address of the memory region
> +    PVOID            virt_addr;  // virtual address of the memory region
> +    PVOID            user_mapped_virt_addr;  // virtual address of the region mapped into user process context
> +};
> +
> +struct dev_addr {
> +    ULONG   bus_num;
> +    USHORT  dev_num;
> +    USHORT  func_num;
> +};

This structure is not needed for interface.

> +
> +enum pci_io {
> +    PCI_IO_READ = 0,
> +    PCI_IO_WRITE = 1
> +};
> +
> +#define PCI_MAX_BAR 6
> +
> +struct device_info
> +{
> +    struct mem_region   hw[PCI_MAX_BAR];
> +    USHORT              reserved;

"Reserved" is not used and it is not documented whether the caller must
initialize it or not. I suggest removing it.

> +};
> +
> +struct dpdk_pci_config_io
> +{
> +    UINT32              offset;
> +    UINT8               op;
> +    UINT32              access_size; // 1, 2, 4, or 8 bytes
> +
> +    union dpdk_pci_config_io_data {
> +        UINT8			u8;
> +        UINT16			u16;
> +        UINT32			u32;
> +        UINT64			u64;
> +    } data;
> +};
> +
> +#pragma pack(pop)
> +
> +#endif // NETUIO_INTERFACE_H
> diff --git a/windows/netuio/netuio_queue.c b/windows/netuio/netuio_queue.c
> new file mode 100644
> index 000000000..cea94e630
> --- /dev/null
> +++ b/windows/netuio/netuio_queue.c
> @@ -0,0 +1,333 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2020 Microsoft Corporation.
> + */
> +
> +#include "netuio_drv.h"
> +
> +#ifdef ALLOC_PRAGMA
> +#pragma alloc_text (PAGE, netuio_queue_initialize)
> +#endif
> +
> +static
> +BOOLEAN
> +netuio_get_usermode_mapping_flag(WDFREQUEST Request)
> +{
[snip]
> +}

Blank line is missing here.

[snip]
> +
> +/*
> +Routine Description:
> +    Maps address ranges into the usermode process's address space.  The following
> +    ranges are mapped:
> +
> +        * Any PCI BARs that our device was assigned
> +        * The scratch buffer of contiguous pages
> +
> +Return Value:
> +    NTSTATUS
> +*/
> +static NTSTATUS
> +netuio_map_address_into_user_process(_In_ PNETUIO_CONTEXT_DATA ctx, WDFREQUEST Request)
[snip]

> +_Use_decl_annotations_
> +VOID
> +netuio_evt_IO_in_caller_context(WDFDEVICE  Device,
> +                                WDFREQUEST Request)
> +{
> +    WDF_REQUEST_PARAMETERS params = { 0 };
> +    NTSTATUS status = STATUS_SUCCESS;
> +    PVOID    output_buf = NULL;
> +    size_t   output_buf_size;
> +    size_t  bytes_returned = 0;
> +    PNETUIO_CONTEXT_DATA  ctx = NULL;
> +
> +    ctx = netuio_get_context_data(Device);
> +
> +    WDF_REQUEST_PARAMETERS_INIT(&params);
> +    WdfRequestGetParameters(Request, &params);
> +
> +    // We only need to be in the context of the process that initiated the request
> +    //when we need to map memory to userspace. Otherwise, send the request back to framework.
> +    if (!((params.Type == WdfRequestTypeDeviceControl) &&
> +        (params.Parameters.DeviceIoControl.IoControlCode == IOCTL_NETUIO_MAP_HW_INTO_USERMODE)))
> +    {
> +        status = WdfDeviceEnqueueRequest(Device, Request);
> +
> +        if (!NT_SUCCESS(status))
> +        {
> +            WdfRequestCompleteWithInformation(Request, status, bytes_returned);
> +        }
> +        return;
> +    }
> +
> +    // Return relevant data to the caller
> +    status = WdfRequestRetrieveOutputBuffer(Request, sizeof(struct device_info), &output_buf, &output_buf_size);
> +    if (!NT_SUCCESS(status)) {
> +        status = STATUS_INVALID_BUFFER_SIZE;

Better propagate original error to help debugging
(two more similar places in netuio_evt_IO_device_control).

> +        goto end;
> +    }
> +
> +    status = netuio_map_address_into_user_process(ctx, Request);
> +    if (status != STATUS_SUCCESS) {
> +        goto end;
> +    }
> +
> +    netuio_set_usermode_mapping_flag(Request);
> +
> +    netuio_handle_get_hw_data_request(ctx, output_buf, output_buf_size);
> +    bytes_returned = output_buf_size;
> +
> +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)
[snip]
> +
> +    if (dpdk_pci_io_input->op == PCI_IO_READ) {
> +        *(UINT64 *)output_buf = 0;
> +        bytes_returned = ctx->bus_interface.GetBusData(
> +            ctx->bus_interface.Context,
> +            PCI_WHICHSPACE_CONFIG,
> +            output_buf,
> +            dpdk_pci_io_input->offset,
> +            dpdk_pci_io_input->access_size);
> +    }
> +    else if (dpdk_pci_io_input->op == PCI_IO_WRITE) {
> +        // returns bytes written
> +        bytes_returned = ctx->bus_interface.SetBusData(
> +            ctx->bus_interface.Context,
> +            PCI_WHICHSPACE_CONFIG,
> +            (PVOID)&dpdk_pci_io_input->data,
> +            dpdk_pci_io_input->offset,
> +            dpdk_pci_io_input->access_size);

The "offset" is not checked, so GetBusData() and SetBusData() can fail and
return 0. You have to either check it above to ensure success or to check if
bytes_returned != dpdk_pci_io_input->access_size and complete with error.

> +    }
> +    else {
> +        status = STATUS_INVALID_PARAMETER;
> +        goto end;
> +    }
> +
> +end:
> +    WdfRequestCompleteWithInformation(Request, status, bytes_returned);
> +
> +    return;
> +}


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [dpdk-dev] [PATCH v2] windows/netuio: add Windows NetUIO kernel driver
  2020-09-09 18:53   ` Narcisa Ana Maria Vasile
@ 2020-09-13 21:39     ` Dmitry Kozlyuk
  0 siblings, 0 replies; 16+ messages in thread
From: Dmitry Kozlyuk @ 2020-09-13 21:39 UTC (permalink / raw)
  To: Narcisa Ana Maria Vasile
  Cc: dev, thomas, ocardona, haramakr, pallavi.kadam, ranjit.menon,
	dmitrym, Narcisa Vasile, Harini Ramakrishnan

On Wed, 9 Sep 2020 11:53:53 -0700, Narcisa Ana Maria Vasile wrote:
> On Mon, Aug 24, 2020 at 11:53:44PM +0300, Dmitry Kozlyuk wrote:
> > On Thu, 20 Aug 2020 15:23:55 -0700, Narcisa Ana Maria Vasile wrote:  
[snip]
> > > +ClassName = "Windows UIO"
> > > +DiskName = "DPDK netUIO Installation Disk"
> > > +netuio.DeviceDesc = "netuio Device"
> > > +netuio.SVCDESC = "netuio Service"
> > > +
> > > +    // Build symbolic link name as <netuio_symbolic_link>_BDF  (bus/device/func)
> > > +    CHAR  symbolic_link[64] = { 0 };
> > > +    sprintf_s(symbolic_link, sizeof(symbolic_link), "%s_%04d%02d%02d",
> > > +                            NETUIO_DEVICE_SYMBOLIC_LINK_ANSI, netuio_contextdata->addr.bus_num,
> > > +                            netuio_contextdata->addr.dev_num, netuio_contextdata->addr.func_num);
> > > +
> > > +    ANSI_STRING ansi_symbolic_link;
> > > +    RtlInitAnsiString(&ansi_symbolic_link, symbolic_link);
> > > +
> > > +    status = RtlAnsiStringToUnicodeString(&netuio_symbolic_link, &ansi_symbolic_link, TRUE);
> > > +    if (!NT_SUCCESS(status))
> > > +        return status;  
> > 
> > Why not use Unicode directly?
> >   
> It looks like either way, a cast will be needed (for example, if I use wchar for symbolic_link and then
> RtlInitUnicodeString()). I've left it as is, but let me know if there's an elegant solution that I didn't see.

It's not that important, really, you can leave the code as-is. FWIW, I meant
something like this:

DECLARE_UNICODE_STRING_SIZE(symbolic_link_name, NETUIO_MAX_SYMLINK_LEN);
RtlUnicodeStringPrintf(
    symbolic_link_name,
    NETUIO_DEVICE_SYMBOLIC_LINK_UNICODE,
    ...);

https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntstrsafe/nf-ntstrsafe-rtlunicodestringprintf 


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [dpdk-dev] [PATCH v4] windows/netuio: add Windows NetUIO kernel driver
  2020-09-09 18:41 ` [dpdk-dev] [PATCH v3] " Narcisa Ana Maria Vasile
  2020-09-13 21:39   ` Dmitry Kozlyuk
@ 2020-09-19  2:52   ` Narcisa Ana Maria Vasile
  2020-09-22 21:25     ` Dmitry Kozlyuk
                       ` (2 more replies)
  1 sibling, 3 replies; 16+ messages in thread
From: Narcisa Ana Maria Vasile @ 2020-09-19  2:52 UTC (permalink / raw)
  To: dev, thomas, ocardona, haramakr, pallavi.kadam, dmitry.kozliuk
  Cc: ranjit.menon, dmitrym, Narcisa Vasile, Harini Ramakrishnan

From: Narcisa Vasile <navasile@microsoft.com>

The Windows netuio kernel driver provides the DPDK userspace application
with direct access to hardware, by mapping the HW registers in userspace
and allowing read/write operations from/to the device
configuration space.

Two IOCTLs are defined by the netuio interface:
  * IOCTL_NETUIO_MAP_HW_INTO_USERMODE
      - used for mapping the device registers into userspace
  * IOCTL_NETUIO_PCI_CONFIG_IO
      - used to read/write from/into the device configuration space

Note:
Requests to map the device BARs into userspace need to be processed
in the thread context of the process that initiated the mapping request.
Otherwise, the BARs might end up mapped into an arbitrary process
address space.
EvtIoInCallerContext is used to ensure the requests are handled
in the right user thread context. Other requests (PCI config)
are sent back to the framework and processed by the EvtIoDeviceControl
callback.

Cc: Harini Ramakrishnan <Harini.Ramakrishnan@microsoft.com>
Cc: Omar Cardona <ocardona@microsoft.com>
Cc: Dmitry Malloy <dmitrym@microsoft.com>
Signed-off-by: Narcisa Vasile <navasile@microsoft.com>
---

v4:
Fixed issues reported by DmitryK:
* Fix whitespaces issues
* Fix status not checked
* Remove NULL checks for device and file context getters
* Use %x for BDF
* Add comment for BAR traversing
* Remove complex calculations that were only needed in ASSERT
* Remove netuio_evt_driver_context_cleanup as not needed
* Check GetBusData/SetBusData returned bytes
* General cleanup

 windows/.gitattributes                |   4 +
 windows/.gitignore                    |   2 +
 windows/netuio/README.rst             |  58 +++++
 windows/netuio/netuio.inf             |  77 ++++++
 windows/netuio/netuio.sln             |  24 ++
 windows/netuio/netuio.vcxproj         | 113 +++++++++
 windows/netuio/netuio.vcxproj.filters |  54 +++++
 windows/netuio/netuio_dev.c           | 273 +++++++++++++++++++++
 windows/netuio/netuio_dev.h           |  66 +++++
 windows/netuio/netuio_drv.c           | 131 ++++++++++
 windows/netuio/netuio_drv.h           |  30 +++
 windows/netuio/netuio_interface.h     |  88 +++++++
 windows/netuio/netuio_queue.c         | 334 ++++++++++++++++++++++++++
 windows/netuio/netuio_queue.h         |  21 ++
 14 files changed, 1275 insertions(+)
 create mode 100644 windows/.gitattributes
 create mode 100644 windows/.gitignore
 create mode 100644 windows/netuio/README.rst
 create mode 100644 windows/netuio/netuio.inf
 create mode 100644 windows/netuio/netuio.sln
 create mode 100644 windows/netuio/netuio.vcxproj
 create mode 100644 windows/netuio/netuio.vcxproj.filters
 create mode 100644 windows/netuio/netuio_dev.c
 create mode 100644 windows/netuio/netuio_dev.h
 create mode 100644 windows/netuio/netuio_drv.c
 create mode 100644 windows/netuio/netuio_drv.h
 create mode 100644 windows/netuio/netuio_interface.h
 create mode 100644 windows/netuio/netuio_queue.c
 create mode 100644 windows/netuio/netuio_queue.h

diff --git a/windows/.gitattributes b/windows/.gitattributes
new file mode 100644
index 000000000..13482db3d
--- /dev/null
+++ b/windows/.gitattributes
@@ -0,0 +1,4 @@
+* text=auto
+
+*.sln text eol=crlf
+*.vcxproj text eol=crlf
diff --git a/windows/.gitignore b/windows/.gitignore
new file mode 100644
index 000000000..543281e8f
--- /dev/null
+++ b/windows/.gitignore
@@ -0,0 +1,2 @@
+x64/
+.vs/
diff --git a/windows/netuio/README.rst b/windows/netuio/README.rst
new file mode 100644
index 000000000..d9c5e3c8d
--- /dev/null
+++ b/windows/netuio/README.rst
@@ -0,0 +1,58 @@
+..  SPDX-License-Identifier: BSD-3-Clause
+    Copyright(c) 2020 Microsoft Corporation.
+
+Compiling the NetUIO Driver from Source
+=======================================
+
+Operating System
+~~~~~~~~~~~~~~~~
+
+The NetUIO source has been validated against the following operating systems:
+
+* Windows Server 2016
+* Windows Server 2019
+
+Hardware Requirements
+~~~~~~~~~~~~~~~~~~~~~
+The NetUIO driver has been validated using the following network adapters on the Windows platform:
+
+*
+*
+
+Software Requirements
+~~~~~~~~~~~~~~~~~~~~~
+For a list of required software tools please see the Prerequisites section in windows/README.rst.
+
+Building the NetUIO Driver
+--------------------------
+
+Follow the steps below to build the NetUIO driver and install the driver for the network adapter.
+
+* Clone the dpdk-kmods repository: git clone git://dpdk.org/dpdk-kmods
+* Navigate to \dpdk-kmods\windows\netuio
+* Load netuio.sln in Microsoft Visual Studio 2017 or 2019
+* Choose Release as the configuration mode and Build the solution
+* The resultant output files can be found in x64\Release\netuio
+
+Installing netuio.sys Driver for development
+--------------------------------------------
+.. note::
+
+   In a development environment, NetUIO driver can be loaded by enabling test-signing.
+   Please implement adequate precautionary measures before installing a test-signed driver, replacing a signed-driver.
+
+To ensure test-signed kernel-mode drivers can load on Windows, enable test-signing, using the following BCDEdit command.
+
+C:\windows\system32>Bcdedit.exe -set TESTSIGNING ON
+
+Windows displays the text "Test Mode" to remind users the system has test-signing enabled.
+Refer to the MSDN documentation on how to Test-Sign a Driver Package.
+
+To procure a WHQL signed NetUIO driver for Windows, please reach out to dpdkwin@microsoft.com
+
+* Go to Device Manager -> Network Adapters.
+* Right Click on target network adapter -> Select Update Driver.
+* Select "Browse my computer for driver software".
+* In the resultant window, select "Let me pick from a list of available drivers on my computer".
+* Select "DPDK netUIO for Network Adapter" from the list of drivers.
+* The NetUIO.sys driver is successfully installed.
diff --git a/windows/netuio/netuio.inf b/windows/netuio/netuio.inf
new file mode 100644
index 000000000..7121a0fa0
--- /dev/null
+++ b/windows/netuio/netuio.inf
@@ -0,0 +1,77 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright (c) Microsoft Corporation. All rights reserved
+;
+; netuio.inf
+;
+
+[Version]
+Signature="$WINDOWS NT$"
+Class=%ClassName%
+ClassGuid={78912BC1-CB8E-4B28-A329-F322EBADBE0F}
+Provider=%Provider%
+CatalogFile=netuio.cat
+DriverVer=
+
+[ClassInstall32]
+Addreg=netuioClassReg
+
+[netuioClassReg]
+HKR,,,0,%ClassName%
+HKR,,Icon,,-5
+
+;*****************************************
+; Install Section
+;*****************************************
+
+[Manufacturer]
+%ManufacturerName%=Standard, NT$ARCH$
+
+[Standard.NT$ARCH$]
+%netuio.DeviceDesc%=netuio_Device, Root\netuio
+
+[netuio_Device.NT]
+CopyFiles=Drivers_Dir
+
+[Drivers_Dir]
+netuio.sys
+
+[netuio_Device.NT.HW]
+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)"
+
+;-------------- Service installation
+[netuio_Device.NT.Services]
+AddService = netuio,%SPSVCINST_ASSOCSERVICE%, netuio_Service_Inst
+
+; -------------- netuio driver install sections
+[netuio_Service_Inst]
+DisplayName    = %netuio.SVCDESC%
+ServiceType    = 1               ; SERVICE_KERNEL_DRIVER
+StartType      = 3               ; SERVICE_DEMAND_START
+ErrorControl   = 1               ; SERVICE_ERROR_NORMAL
+ServiceBinary  = %12%\netuio.sys
+AddReg         = DMAr.reg
+
+[DestinationDirs]
+DefaultDestDir = 12
+
+[SourceDisksNames]
+1 = %DiskName%,,,""
+
+[SourceDisksFiles]
+netuio.sys  = 1,,
+
+[Strings]
+SPSVCINST_ASSOCSERVICE= 0x00000002
+ManufacturerName = "Microsoft"
+Provider = "Vendor"
+ClassName = "Windows UIO"
+DiskName = "DPDK netUIO Installation Disk"
+netuio.DeviceDesc = "netuio Device"
+netuio.SVCDESC = "netuio Service"
+
+[DMAr.reg]
+HKR,Parameters,DmaRemappingCompatible,0x00010001,1
diff --git a/windows/netuio/netuio.sln b/windows/netuio/netuio.sln
new file mode 100644
index 000000000..15c26e6b9
--- /dev/null
+++ b/windows/netuio/netuio.sln
@@ -0,0 +1,24 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.25420.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "netuio", "netuio.vcxproj", "{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|x64 = Debug|x64
+		Release|x64 = Release|x64
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}.Debug|x64.ActiveCfg = Debug|x64
+		{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}.Debug|x64.Build.0 = Debug|x64
+		{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}.Debug|x64.Deploy.0 = Debug|x64
+		{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}.Release|x64.ActiveCfg = Release|x64
+		{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}.Release|x64.Build.0 = Release|x64
+		{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}.Release|x64.Deploy.0 = Release|x64
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal
diff --git a/windows/netuio/netuio.vcxproj b/windows/netuio/netuio.vcxproj
new file mode 100644
index 000000000..87c8b940c
--- /dev/null
+++ b/windows/netuio/netuio.vcxproj
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}</ProjectGuid>
+    <TemplateGuid>{497e31cb-056b-4f31-abb8-447fd55ee5a5}</TemplateGuid>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <MinimumVisualStudioVersion>12.0</MinimumVisualStudioVersion>
+    <Configuration>Debug</Configuration>
+    <Platform Condition="'$(Platform)' == ''">Win32</Platform>
+    <RootNamespace>netuio</RootNamespace>
+    <WindowsTargetPlatformVersion>$(LatestTargetPlatformVersion)</WindowsTargetPlatformVersion>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <TargetVersion>
+    </TargetVersion>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
+    <ConfigurationType>Driver</ConfigurationType>
+    <DriverType>KMDF</DriverType>
+    <DriverTargetPlatform>Desktop</DriverTargetPlatform>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <TargetVersion>
+    </TargetVersion>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
+    <ConfigurationType>Driver</ConfigurationType>
+    <DriverType>KMDF</DriverType>
+    <DriverTargetPlatform>Desktop</DriverTargetPlatform>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
+    <OutDir>$(SolutionDir)$(Platform)\$(ConfigurationName)\$(MSBuildProjectName)\</OutDir>
+    <IntDir>$(SolutionDir)$(Platform)\$(ConfigurationName)\$(MSBuildProjectName)\</IntDir>
+    <Inf2CatUseLocalTime>true</Inf2CatUseLocalTime>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
+    <OutDir>$(SolutionDir)$(Platform)\$(ConfigurationName)\$(MSBuildProjectName)\</OutDir>
+    <IntDir>$(SolutionDir)$(Platform)\$(ConfigurationName)\$(MSBuildProjectName)\</IntDir>
+    <Inf2CatUseLocalTime>true</Inf2CatUseLocalTime>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <WppEnabled>false</WppEnabled>
+      <WppRecorderEnabled>true</WppRecorderEnabled>
+      <WppScanConfigurationData Condition="'%(ClCompile.ScanConfigurationData)' == ''">trace.h</WppScanConfigurationData>
+      <WppKernelMode>true</WppKernelMode>
+      <WppMinimalRebuildFromTracking>false</WppMinimalRebuildFromTracking>
+    </ClCompile>
+    <Inf>
+      <TimeStamp>0.6.1</TimeStamp>
+    </Inf>
+    <Link>
+      <AdditionalDependencies>%(AdditionalDependencies);$(KernelBufferOverflowLib);$(DDK_LIB_PATH)ntoskrnl.lib;$(DDK_LIB_PATH)hal.lib;$(DDK_LIB_PATH)wmilib.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfLdr.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfDriverEntry.lib;$(DDK_LIB_PATH)\wdmsec.lib</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <WppEnabled>false</WppEnabled>
+      <WppRecorderEnabled>true</WppRecorderEnabled>
+      <WppScanConfigurationData Condition="'%(ClCompile.ScanConfigurationData)' == ''">trace.h</WppScanConfigurationData>
+      <WppKernelMode>true</WppKernelMode>
+      <WppMinimalRebuildFromTracking>false</WppMinimalRebuildFromTracking>
+    </ClCompile>
+    <Inf>
+      <TimeStamp>0.6.1</TimeStamp>
+    </Inf>
+    <Link>
+      <AdditionalDependencies>%(AdditionalDependencies);$(KernelBufferOverflowLib);$(DDK_LIB_PATH)ntoskrnl.lib;$(DDK_LIB_PATH)hal.lib;$(DDK_LIB_PATH)wmilib.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfLdr.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfDriverEntry.lib;$(DDK_LIB_PATH)\wdmsec.lib</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <FilesToPackage Include="$(TargetPath)" />
+  </ItemGroup>
+  <ItemGroup>
+    <Inf Include="netuio.inf" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="netuio_dev.h" />
+    <ClInclude Include="netuio_drv.h" />
+    <ClInclude Include="netuio_interface.h" />
+    <ClInclude Include="netuio_queue.h" />
+    <ClInclude Include="resource.h" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="netuio_dev.c" />
+    <ClCompile Include="netuio_drv.c" />
+    <ClCompile Include="netuio_queue.c" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>
\ No newline at end of file
diff --git a/windows/netuio/netuio.vcxproj.filters b/windows/netuio/netuio.vcxproj.filters
new file mode 100644
index 000000000..49c1da15b
--- /dev/null
+++ b/windows/netuio/netuio.vcxproj.filters
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <Filter Include="Source Files">
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+    </Filter>
+    <Filter Include="Header Files">
+      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+      <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
+    </Filter>
+    <Filter Include="Resource Files">
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+    </Filter>
+    <Filter Include="Driver Files">
+      <UniqueIdentifier>{8E41214B-6785-4CFE-B992-037D68949A14}</UniqueIdentifier>
+      <Extensions>inf;inv;inx;mof;mc;</Extensions>
+    </Filter>
+  </ItemGroup>
+  <ItemGroup>
+    <Inf Include="netuio.inf">
+      <Filter>Driver Files</Filter>
+    </Inf>
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="netuio_interface.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="netuio_dev.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="netuio_drv.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="netuio_queue.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="resource.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="netuio_queue.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="netuio_dev.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="netuio_drv.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+  </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/windows/netuio/netuio_dev.c b/windows/netuio/netuio_dev.c
new file mode 100644
index 000000000..b2deb10c5
--- /dev/null
+++ b/windows/netuio/netuio_dev.c
@@ -0,0 +1,273 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Microsoft Corporation.
+ */
+
+#include <stdio.h>
+#include "netuio_drv.h"
+
+#include <wdmguid.h>
+#include <ntstrsafe.h>
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text (PAGE, netuio_create_device)
+#pragma alloc_text (PAGE, netuio_map_hw_resources)
+#pragma alloc_text (PAGE, netuio_free_hw_resources)
+#endif
+
+static NTSTATUS
+get_pci_device_info(_In_ WDFOBJECT device)
+{
+    NTSTATUS status = STATUS_UNSUCCESSFUL;
+
+    PNETUIO_CONTEXT_DATA  ctx;
+    ctx = netuio_get_context_data(device);
+
+    ctx->wdf_device = device;  // Store for later use
+
+    // Obtain the BUS_INTERFACE_STANDARD interface from the Bus Driver
+    status = WdfFdoQueryForInterface(device,
+                                     &GUID_BUS_INTERFACE_STANDARD,
+                                     (PINTERFACE)&ctx->bus_interface,
+                                     sizeof(BUS_INTERFACE_STANDARD),
+                                     1,
+                                     NULL);
+    if (!NT_SUCCESS(status))
+        return status;
+
+    // Retrieve the B:D:F details of our device
+    PDEVICE_OBJECT pdo = NULL;
+    pdo = WdfDeviceWdmGetPhysicalDevice(device);
+    if (pdo) {
+        ULONG prop = 0, length = 0;
+        status = IoGetDeviceProperty(pdo, DevicePropertyBusNumber, sizeof(ULONG), (PVOID)&ctx->addr.bus_num, &length);
+        if (!NT_SUCCESS(status)) {
+            return status;
+        }
+
+        status = IoGetDeviceProperty(pdo, DevicePropertyAddress, sizeof(ULONG), (PVOID)&prop, &length);
+        if (!NT_SUCCESS(status)) {
+            return status;
+        }
+
+        ctx->addr.func_num = prop & 0x0000FFFF;
+        ctx->addr.dev_num = ((prop >> 16) & 0x0000FFFF);
+    }
+
+    return status;
+}
+
+static NTSTATUS
+create_device_specific_symbolic_link(_In_ WDFOBJECT device)
+{
+    DECLARE_UNICODE_STRING_SIZE(symbolic_link, NETUIO_MAX_SYMLINK_LEN);
+
+    PNETUIO_CONTEXT_DATA  ctx;
+    ctx = netuio_get_context_data(device);
+
+    // Build symbolic link name as <netuio_symbolic_link>_BDF  (bus/device/func)
+    RtlUnicodeStringPrintf(&symbolic_link,
+                           L"%s_%04x%02x%02x",
+                           NETUIO_DEVICE_SYMBOLIC_LINK_UNICODE,
+                           ctx->addr.bus_num,
+                           ctx->addr.dev_num,
+                           ctx->addr.func_num);
+
+    return WdfDeviceCreateSymbolicLink(device, &symbolic_link);
+}
+
+/*
+Routine Description:
+    Worker routine called to create a device and its software resources.
+
+Return Value:
+    NTSTATUS
+ */
+_Use_decl_annotations_
+NTSTATUS
+netuio_create_device(PWDFDEVICE_INIT DeviceInit)
+{
+    NTSTATUS status;
+    WDFDEVICE device = NULL;
+
+    WDF_OBJECT_ATTRIBUTES deviceAttributes;
+    WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;
+    WDF_FILEOBJECT_CONFIG fileConfig;
+    WDF_OBJECT_ATTRIBUTES fileAttributes;
+
+    PAGED_CODE();
+
+    // Register PnP power management callbacks
+    WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
+    pnpPowerCallbacks.EvtDevicePrepareHardware = netuio_evt_prepare_hw;
+    pnpPowerCallbacks.EvtDeviceReleaseHardware = netuio_evt_release_hw;
+    WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);
+
+    // Register callbacks for when a HANDLE is opened or closed.
+    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&fileAttributes, NETUIO_FILE_CONTEXT_DATA);
+    WDF_FILEOBJECT_CONFIG_INIT(&fileConfig, WDF_NO_EVENT_CALLBACK, WDF_NO_EVENT_CALLBACK, netuio_evt_file_cleanup);
+    WdfDeviceInitSetFileObjectConfig(DeviceInit, &fileConfig, &fileAttributes);
+
+    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, NETUIO_CONTEXT_DATA);
+    WdfDeviceInitSetIoInCallerContextCallback(DeviceInit, netuio_evt_IO_in_caller_context);
+
+    status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);
+
+    if (NT_SUCCESS(status)) {
+        // Create a device interface so that applications can find and talk to us.
+        status = WdfDeviceCreateDeviceInterface(device, &GUID_DEVINTERFACE_netUIO, NULL);
+    }
+
+    if (NT_SUCCESS(status)) {
+        // Retrieve and store PCI information
+        status = get_pci_device_info(device);
+    }
+
+    if (NT_SUCCESS(status)) {
+        // Create a symbolic link name for user-space access
+        status = create_device_specific_symbolic_link(device);
+    }
+
+    if (NT_SUCCESS(status)) {
+        // Initialize the I/O Package and any Queues
+        status = netuio_queue_initialize(device);
+    }
+
+    return status;
+}
+
+_Use_decl_annotations_
+NTSTATUS
+netuio_map_hw_resources(WDFDEVICE Device, WDFCMRESLIST Resources, WDFCMRESLIST ResourcesTranslated)
+{
+    UNREFERENCED_PARAMETER(Resources);
+
+    NTSTATUS status;
+
+    PNETUIO_CONTEXT_DATA  ctx;
+    ctx = netuio_get_context_data(Device);
+
+    PCI_COMMON_HEADER pci_config = {0};
+    ULONG bytes_returned;
+
+    // Read PCI configuration space
+    bytes_returned = ctx->bus_interface.GetBusData(
+        ctx->bus_interface.Context,
+        PCI_WHICHSPACE_CONFIG,
+        &pci_config,
+        0,
+        sizeof(pci_config));
+
+    if (bytes_returned != sizeof(pci_config)) {
+        status = STATUS_NOT_SUPPORTED;
+        goto end;
+    }
+
+    // Device type is implictly enforced by .inf
+    ASSERT(PCI_CONFIGURATION_TYPE(&pci_config) == PCI_DEVICE_TYPE);
+
+    RtlZeroMemory(ctx->bar, sizeof(ctx->bar));
+    RtlZeroMemory(ctx->dpdk_hw, sizeof(ctx->dpdk_hw));
+
+    PCM_PARTIAL_RESOURCE_DESCRIPTOR descriptor;
+    ULONG next_descriptor = 0;
+    ULONG curr_bar = 0;
+    ULONG prev_bar = 0;
+
+   /*
+    * ResourcesTranslated report MMIO BARs in the correct order, but their
+    * indices differ from physical ones.
+
+    * For example:
+    * The values returned by WdfCmResourceListGetDescriptor can be condensed like this:
+    * Bar[0] = c840000c
+    * Bar[1] = c800000c
+    * Bar[2] = c844000c
+
+    * But the BAR array has to be filled like this :
+    * Bar[0] = c840000c
+    * Bar[1] = null
+    * Bar[2] = c800000c
+    * Bar[3] = null
+    * Bar[4] = c844000c
+    * Bar[5] = null
+
+    * Traverse PCI configuration manually to maintain physical index,
+    * searching for the next MMIO resource each time.
+    */
+    for (INT bar_index = 0; bar_index < PCI_MAX_BAR; bar_index++) {
+        prev_bar = curr_bar;
+        curr_bar = pci_config.u.type0.BaseAddresses[bar_index];
+        if (curr_bar == 0 || (prev_bar & PCI_TYPE_64BIT)) {
+            continue;
+        }
+
+        // Find next CmResourceTypeMemory
+        do {
+            descriptor = WdfCmResourceListGetDescriptor(ResourcesTranslated, next_descriptor);
+            next_descriptor++;
+
+            if (descriptor == NULL) {
+                status = STATUS_DEVICE_CONFIGURATION_ERROR;
+                goto end;
+            }
+        } while ((descriptor->Type != CmResourceTypeMemory) ||
+                 !(descriptor->Flags & CM_RESOURCE_MEMORY_BAR));
+
+        // Retrieve and map the BARs
+        ctx->bar[bar_index].base_addr.QuadPart = descriptor->u.Memory.Start.QuadPart;
+        ctx->bar[bar_index].size = descriptor->u.Memory.Length;
+        ctx->bar[bar_index].virt_addr = MmMapIoSpace(descriptor->u.Memory.Start,
+                                                     descriptor->u.Memory.Length,
+                                                     MmNonCached);
+        if (ctx->bar[bar_index].virt_addr == NULL) {
+            status = STATUS_INSUFFICIENT_RESOURCES;
+            goto end;
+        }
+
+        // Allocate an MDL for the device BAR, so we can map it to the user's process context later.
+        ctx->dpdk_hw[bar_index].mdl = IoAllocateMdl(ctx->bar[bar_index].virt_addr,
+                                                    (ULONG)ctx->bar[bar_index].size,
+                                                    FALSE,
+                                                    FALSE,
+                                                    NULL);
+        if (!ctx->dpdk_hw[bar_index].mdl) {
+            status = STATUS_INSUFFICIENT_RESOURCES;
+            goto end;
+        }
+
+        ctx->dpdk_hw[bar_index].mem.size = ctx->bar[bar_index].size;
+    } // for bar_index
+
+    status = STATUS_SUCCESS;
+
+end:
+    if (status != STATUS_SUCCESS) {
+        netuio_free_hw_resources(Device);
+    }
+
+    return status;
+}
+
+_Use_decl_annotations_
+VOID
+netuio_free_hw_resources(WDFDEVICE Device)
+{
+    PNETUIO_CONTEXT_DATA  ctx;
+    ctx = netuio_get_context_data(Device);
+
+    for (UINT8 bar_index = 0; bar_index < PCI_MAX_BAR; bar_index++) {
+
+        // Free the allocated MDLs
+        if (ctx->dpdk_hw[bar_index].mdl) {
+            IoFreeMdl(ctx->dpdk_hw[bar_index].mdl);
+        }
+
+        // Unmap all the BAR regions previously mapped
+        if (ctx->bar[bar_index].virt_addr) {
+            MmUnmapIoSpace(ctx->bar[bar_index].virt_addr, ctx->bar[bar_index].size);
+        }
+    }
+
+    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
new file mode 100644
index 000000000..62851141d
--- /dev/null
+++ b/windows/netuio/netuio_dev.h
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Microsoft Corporation.
+ */
+
+#ifndef NETUIO_DEV_H
+#define NETUIO_DEV_H
+
+#include "netuio_interface.h"
+
+struct pci_bar {
+    PHYSICAL_ADDRESS base_addr;
+    PVOID            virt_addr;
+    UINT64           size;
+};
+
+struct mem_map_region {
+    PMDL               mdl;    /**< MDL describing the memory region */
+    struct mem_region  mem;    /**< Memory region details */
+};
+
+struct dev_addr {
+    ULONG   bus_num;
+    USHORT  dev_num;
+    USHORT  func_num;
+};
+
+/**
+ * The device context performs the same job as a WDM device extension in the driver frameworks
+ */
+typedef struct _NETUIO_CONTEXT_DATA
+{
+    WDFDEVICE               wdf_device;             /**< WDF device handle to the FDO */
+    BUS_INTERFACE_STANDARD  bus_interface;          /**< Bus interface for config space access */
+    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 */
+} NETUIO_CONTEXT_DATA, *PNETUIO_CONTEXT_DATA;
+
+
+/**
+ * This macro will generate an inline function called DeviceGetContext
+ * which will be used to get a pointer to the device context memory in a
+ * type safe manner.
+ */
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(NETUIO_CONTEXT_DATA, netuio_get_context_data)
+
+typedef struct
+{
+    BOOLEAN bMapped;     /**< value is set to TRUE if the User-mode mapping was done for this file object. */
+}  NETUIO_FILE_CONTEXT_DATA, * PNETUIO_FILE_CONTEXT_DATA;
+
+
+/**
+ * This macro will generate an inline function which will be used to get
+ * a pointer to the file object's context memory in a type safe manner.
+ */
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(NETUIO_FILE_CONTEXT_DATA, netuio_get_file_object_context_data)
+
+/**
+ * Functions to initialize the device and its callbacks
+ */
+NTSTATUS netuio_create_device(_Inout_ PWDFDEVICE_INIT DeviceInit);
+NTSTATUS netuio_map_hw_resources(_In_ WDFDEVICE Device, _In_ WDFCMRESLIST Resources, _In_ WDFCMRESLIST ResourcesTranslated);
+VOID netuio_free_hw_resources(_In_ WDFDEVICE Device);
+
+#endif // NETUIO_DEV_H
diff --git a/windows/netuio/netuio_drv.c b/windows/netuio/netuio_drv.c
new file mode 100644
index 000000000..2d0fb3bd2
--- /dev/null
+++ b/windows/netuio/netuio_drv.c
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Microsoft Corporation.
+ */
+
+#include "netuio_drv.h"
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text (INIT, DriverEntry)
+#pragma alloc_text (PAGE, netuio_evt_device_add)
+#endif
+
+/*
+Routine Description:
+    DriverEntry initializes the driver and is the first routine called by the
+    system after the driver is loaded. DriverEntry specifies the other entry
+    points in the function driver, such as EvtDevice and DriverUnload.
+
+Return Value:
+    STATUS_SUCCESS if successful,
+    STATUS_UNSUCCESSFUL otherwise.
+ */
+NTSTATUS
+DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath)
+{
+    WDF_DRIVER_CONFIG config;
+    NTSTATUS status;
+    WDF_OBJECT_ATTRIBUTES attributes;
+
+    WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+    WDF_DRIVER_CONFIG_INIT(&config, netuio_evt_device_add);
+
+    status = WdfDriverCreate(DriverObject, RegistryPath,
+                             &attributes, &config,
+                             WDF_NO_HANDLE);
+
+    if (!NT_SUCCESS(status)) {
+        return status;
+    }
+
+    return status;
+}
+
+/*
+Routine Description:
+    netuio_evt_device_add is called by the framework in response to AddDevice
+    call from the PnP manager. We create and initialize a device object to
+    represent a new instance of the device.
+
+Return Value:
+    NTSTATUS
+ */
+_Use_decl_annotations_
+NTSTATUS
+netuio_evt_device_add(WDFDRIVER Driver, PWDFDEVICE_INIT DeviceInit)
+{
+    UNREFERENCED_PARAMETER(Driver);
+    return netuio_create_device(DeviceInit);
+}
+
+/*
+Routine Description :
+    Maps HW resources and retrieves the PCI BAR address(es) of the device
+
+Return Value :
+    STATUS_SUCCESS is successful.
+    STATUS_<ERROR> otherwise
+-*/
+_Use_decl_annotations_
+NTSTATUS
+netuio_evt_prepare_hw(WDFDEVICE Device, WDFCMRESLIST Resources, WDFCMRESLIST ResourcesTranslated)
+{
+    NTSTATUS status;
+
+    status = netuio_map_hw_resources(Device, Resources, ResourcesTranslated);
+    if (!NT_SUCCESS(status)) {
+        return status;
+    }
+
+    PNETUIO_CONTEXT_DATA  ctx = netuio_get_context_data(Device);
+    DbgPrintEx(DPFLTR_IHVNETWORK_ID, DPFLTR_NETUIO_INFO_LEVEL, "netUIO Driver loaded...on device (B:D:F) %04d:%02d:%02d\n",
+        ctx->addr.bus_num, ctx->addr.dev_num, ctx->addr.func_num);
+
+    return status;
+}
+
+/*
+Routine Description :
+    Releases the resource mapped by netuio_evt_prepare_hw
+
+Return Value :
+    STATUS_SUCCESS always.
+-*/
+_Use_decl_annotations_
+NTSTATUS
+netuio_evt_release_hw(WDFDEVICE Device, WDFCMRESLIST ResourcesTranslated)
+{
+    UNREFERENCED_PARAMETER(ResourcesTranslated);
+
+    netuio_free_hw_resources(Device);
+
+    return STATUS_SUCCESS;
+}
+
+/*
+Routine Description :
+    EVT_WDF_FILE_CLEANUP callback, called when user process handle is closed.
+
+    Undoes IOCTL_NETUIO_MAP_HW_INTO_USERMODE.
+
+Return value :
+    None
+-*/
+_Use_decl_annotations_
+VOID
+netuio_evt_file_cleanup(WDFFILEOBJECT FileObject)
+{
+    PNETUIO_FILE_CONTEXT_DATA file_ctx;
+
+    file_ctx = netuio_get_file_object_context_data(FileObject);
+
+    if (file_ctx->bMapped == TRUE)
+    {
+        WDFDEVICE device;
+        PNETUIO_CONTEXT_DATA ctx;
+
+        device = WdfFileObjectGetDevice(FileObject);
+        ctx = netuio_get_context_data(device);
+        netuio_unmap_address_from_user_process(ctx);
+        file_ctx->bMapped = FALSE;
+    }
+}
diff --git a/windows/netuio/netuio_drv.h b/windows/netuio/netuio_drv.h
new file mode 100644
index 000000000..469c6ac60
--- /dev/null
+++ b/windows/netuio/netuio_drv.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Microsoft Corporation.
+ */
+
+#ifndef NETUIO_DRV_H
+#define NETUIO_DRV_H
+
+#define INITGUID
+
+#include <ntddk.h>
+#include <wdf.h>
+
+#include "netuio_dev.h"
+#include "netuio_queue.h"
+
+/**
+ * Print output constants
+ */
+#define DPFLTR_NETUIO_INFO_LEVEL   35
+
+/**
+ * WDFDRIVER Events
+ */
+DRIVER_INITIALIZE DriverEntry;
+EVT_WDF_DRIVER_DEVICE_ADD       netuio_evt_device_add;
+EVT_WDF_DEVICE_PREPARE_HARDWARE netuio_evt_prepare_hw;
+EVT_WDF_DEVICE_RELEASE_HARDWARE netuio_evt_release_hw;
+EVT_WDF_FILE_CLOSE              netuio_evt_file_cleanup;
+
+#endif // NETUIO_DRV_H
diff --git a/windows/netuio/netuio_interface.h b/windows/netuio/netuio_interface.h
new file mode 100644
index 000000000..232b5bc2d
--- /dev/null
+++ b/windows/netuio/netuio_interface.h
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Microsoft Corporation.
+ */
+
+ /**
+  * @file netuio kernel driver interface
+  */
+
+#ifndef NETUIO_INTERFACE_H
+#define NETUIO_INTERFACE_H
+
+/**
+ * All structures in this file are packed on an 8B boundary.
+ */
+#pragma pack(push)
+#pragma pack(8)
+
+/**
+ * Define an Interface Guid so that any app can find the device and talk to it.
+ */
+DEFINE_GUID (GUID_DEVINTERFACE_netUIO, 0x08336f60,0x0679,0x4c6c,0x85,0xd2,0xae,0x7c,0xed,0x65,0xff,0xf7); // {08336f60-0679-4c6c-85d2-ae7ced65fff7}
+
+/**
+ * Device name definitions
+ */
+#define NETUIO_DEVICE_SYMBOLIC_LINK_UNICODE    L"\\DosDevices\\netuio"
+#define NETUIO_MAX_SYMLINK_LEN                 255
+
+/**
+ * IOCTL_NETUIO_MAP_HW_INTO_USERMODE is used for mapping the device registers
+ * into userspace. It returns the physical address, virtual address
+ * and the size of the memory region where the BARs were mapped.
+ */
+#define IOCTL_NETUIO_MAP_HW_INTO_USERMODE CTL_CODE(FILE_DEVICE_NETWORK, 51, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
+
+/**
+ * IOCTL_NETUIO_PCI_CONFIG_IO is used to read/write from/into the device
+ * configuration space.
+ *
+ * Input:
+ *   - the operation type (read/write)
+ *   - the offset into the device data where the operation begins
+ *   - the length of data to read/write.
+ *   - in case of a write operation, the data to be written to the device
+ *     configuration space.
+ *
+ * Output:
+ *   - in case of a read operation, the output buffer is filled
+ *     with the data read from the configuration space.
+ */
+#define IOCTL_NETUIO_PCI_CONFIG_IO        CTL_CODE(FILE_DEVICE_NETWORK, 52, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
+
+struct mem_region {
+    UINT64           size;       /**< memory region size */
+    LARGE_INTEGER    phys_addr;  /**< physical address of the memory region */
+    PVOID            virt_addr;  /**< virtual address of the memory region */
+    PVOID            user_mapped_virt_addr;  /**< virtual address of the region mapped into user process context */
+};
+
+enum pci_io {
+    PCI_IO_READ = 0,
+    PCI_IO_WRITE = 1
+};
+
+#define PCI_MAX_BAR 6
+
+struct device_info
+{
+    struct mem_region   hw[PCI_MAX_BAR];
+};
+
+struct dpdk_pci_config_io
+{
+    UINT32              offset; /**< offset into the device config space where the reading/writing starts */
+    UINT8               op; /**< operation type: read or write */
+    UINT32              access_size; /**< 1, 2, 4, or 8 bytes */
+
+    union dpdk_pci_config_io_data {
+        UINT8			u8;
+        UINT16			u16;
+        UINT32			u32;
+        UINT64			u64;
+    } data; /**< Data to be written, in case of write operations */
+};
+
+#pragma pack(pop)
+
+#endif // NETUIO_INTERFACE_H
diff --git a/windows/netuio/netuio_queue.c b/windows/netuio/netuio_queue.c
new file mode 100644
index 000000000..f7f75d14f
--- /dev/null
+++ b/windows/netuio/netuio_queue.c
@@ -0,0 +1,334 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Microsoft Corporation.
+ */
+
+#include "netuio_drv.h"
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text (PAGE, netuio_queue_initialize)
+#endif
+
+static
+BOOLEAN
+netuio_get_usermode_mapping_flag(WDFREQUEST Request)
+{
+    WDFFILEOBJECT file_object;
+
+    file_object = WdfRequestGetFileObject(Request);
+    if (file_object == NULL)
+    {
+        goto end;
+    }
+
+    PNETUIO_FILE_CONTEXT_DATA file_ctx;
+
+    file_ctx = netuio_get_file_object_context_data(file_object);
+    if (file_ctx->bMapped)
+    {
+        return TRUE;
+    }
+
+end:
+    return FALSE;
+}
+
+static
+VOID
+netuio_set_usermode_mapping_flag(WDFREQUEST Request)
+{
+    WDFFILEOBJECT file_object;
+
+    file_object = WdfRequestGetFileObject(Request);
+    if (file_object == NULL)
+    {
+        return;
+    }
+
+    PNETUIO_FILE_CONTEXT_DATA file_ctx;
+
+    file_ctx = netuio_get_file_object_context_data(file_object);
+
+    file_ctx->bMapped = TRUE;
+}
+
+static void
+netuio_handle_get_hw_data_request(_In_ PNETUIO_CONTEXT_DATA ctx,
+                                  _In_ PVOID outputBuf, _In_ size_t outputBufSize)
+{
+    ASSERT(outputBufSize == sizeof(struct device_info));
+
+    struct device_info *dpdk_pvt_info = (struct device_info *)outputBuf;
+    RtlZeroMemory(dpdk_pvt_info, outputBufSize);
+
+    for (ULONG idx = 0; idx < PCI_MAX_BAR; idx++) {
+        dpdk_pvt_info->hw[idx].phys_addr.QuadPart = ctx->bar[idx].base_addr.QuadPart;
+        dpdk_pvt_info->hw[idx].user_mapped_virt_addr = ctx->dpdk_hw[idx].mem.user_mapped_virt_addr;
+        dpdk_pvt_info->hw[idx].size = ctx->bar[idx].size;
+    }
+}
+
+/*
+Routine Description:
+    Maps address ranges into the usermode process's address space.  The following
+    ranges are mapped:
+
+        * Any PCI BARs that our device was assigned
+        * The scratch buffer of contiguous pages
+
+Return Value:
+    NTSTATUS
+*/
+static NTSTATUS
+netuio_map_address_into_user_process(_In_ PNETUIO_CONTEXT_DATA ctx, WDFREQUEST Request)
+{
+    NTSTATUS status = STATUS_SUCCESS;
+
+    if (netuio_get_usermode_mapping_flag(Request))
+    {
+        goto end;
+    }
+
+    // Map any device BAR(s) to the user's process context
+    for (INT idx = 0; idx < PCI_MAX_BAR; idx++) {
+        if (ctx->dpdk_hw[idx].mdl == NULL) {
+            continue;
+        }
+
+        MmBuildMdlForNonPagedPool(ctx->dpdk_hw[idx].mdl);
+        __try {
+            ctx->dpdk_hw[idx].mem.user_mapped_virt_addr =
+                MmMapLockedPagesSpecifyCache(ctx->dpdk_hw[idx].mdl, UserMode,
+                                             MmCached, NULL, FALSE, NormalPagePriority);
+
+            if (ctx->dpdk_hw[idx].mem.user_mapped_virt_addr == NULL) {
+                status = STATUS_INSUFFICIENT_RESOURCES;
+                goto end;
+            }
+        }
+        __except (EXCEPTION_EXECUTE_HANDLER) {
+            status = GetExceptionCode();
+            goto end;
+        }
+    }
+
+end:
+    if (status != STATUS_SUCCESS) {
+        netuio_unmap_address_from_user_process(ctx);
+    }
+
+    return status;
+}
+
+/*
+Routine Description:
+    Unmaps all address ranges from the usermode process address space.
+    MUST be called in the context of the same process which created
+    the mapping.
+
+Return Value:
+    None
+ */
+_Use_decl_annotations_
+VOID
+netuio_unmap_address_from_user_process(PNETUIO_CONTEXT_DATA ctx)
+{
+    for (INT idx = 0; idx < PCI_MAX_BAR; idx++) {
+        if (ctx->dpdk_hw[idx].mem.user_mapped_virt_addr != NULL) {
+            MmUnmapLockedPages(ctx->dpdk_hw[idx].mem.user_mapped_virt_addr,
+                               ctx->dpdk_hw[idx].mdl);
+
+            ctx->dpdk_hw[idx].mem.user_mapped_virt_addr = NULL;
+        }
+    }
+}
+
+/*
+Routine Description:
+    The I/O dispatch callbacks for the frameworks device object are configured here.
+    A single default I/O Queue is configured for parallel request processing, and a
+    driver context memory allocation is created to hold our structure QUEUE_CONTEXT.
+
+Return Value:
+    None
+ */
+_Use_decl_annotations_
+NTSTATUS
+netuio_queue_initialize(WDFDEVICE Device)
+{
+    WDFQUEUE queue;
+    NTSTATUS status;
+    WDF_IO_QUEUE_CONFIG    queueConfig;
+
+    PAGED_CODE();
+
+    // Configure a default queue so that requests that are not
+    // configure-fowarded using WdfDeviceConfigureRequestDispatching to goto
+    // other queues get dispatched here.
+    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig, WdfIoQueueDispatchParallel);
+
+    queueConfig.EvtIoDeviceControl = netuio_evt_IO_device_control;
+
+    status = WdfIoQueueCreate(Device,
+                              &queueConfig,
+                              WDF_NO_OBJECT_ATTRIBUTES,
+                              &queue);
+
+    if( !NT_SUCCESS(status) ) {
+        return status;
+    }
+
+    return status;
+}
+
+/*
+Routine Description:
+    This routine is invoked to preprocess an I/O request before being placed into a queue.
+    It is guaranteed that it executes in the context of the process that generated the request.
+
+Return Value:
+    None
+ */
+_Use_decl_annotations_
+VOID
+netuio_evt_IO_in_caller_context(WDFDEVICE  Device,
+                                WDFREQUEST Request)
+{
+    WDF_REQUEST_PARAMETERS params = { 0 };
+    NTSTATUS status = STATUS_SUCCESS;
+    PVOID    output_buf = NULL;
+    size_t   output_buf_size;
+    size_t  bytes_returned = 0;
+    PNETUIO_CONTEXT_DATA  ctx = NULL;
+
+    ctx = netuio_get_context_data(Device);
+
+    WDF_REQUEST_PARAMETERS_INIT(&params);
+    WdfRequestGetParameters(Request, &params);
+
+    if (params.Type != WdfRequestTypeDeviceControl)
+    {
+        status = STATUS_INVALID_DEVICE_REQUEST;
+        goto end;
+    }
+
+    // We only need to be in the context of the process that initiated the request
+    //when we need to map memory to userspace. Otherwise, send the request back to framework.
+    if (params.Parameters.DeviceIoControl.IoControlCode != IOCTL_NETUIO_MAP_HW_INTO_USERMODE)
+    {
+        status = WdfDeviceEnqueueRequest(Device, Request);
+        if (!NT_SUCCESS(status))
+        {
+            goto end;
+        }
+        return;
+    }
+
+    // Return relevant data to the caller
+    status = WdfRequestRetrieveOutputBuffer(Request, sizeof(struct device_info), &output_buf, &output_buf_size);
+    if (!NT_SUCCESS(status)) {
+        goto end;
+    }
+
+    status = netuio_map_address_into_user_process(ctx, Request);
+    if (!NT_SUCCESS(status)) {
+        goto end;
+    }
+
+    netuio_set_usermode_mapping_flag(Request);
+
+    netuio_handle_get_hw_data_request(ctx, output_buf, output_buf_size);
+    bytes_returned = output_buf_size;
+
+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)
+{
+    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);
+
+    PNETUIO_CONTEXT_DATA  ctx;
+    ctx = netuio_get_context_data(device);
+
+    if (IoControlCode != IOCTL_NETUIO_PCI_CONFIG_IO)
+    {
+        status = STATUS_INVALID_DEVICE_REQUEST;
+        goto end;
+    }
+
+    // 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);
+    if (!NT_SUCCESS(status)) {
+        goto end;
+    }
+
+    struct dpdk_pci_config_io *dpdk_pci_io_input = (struct dpdk_pci_config_io *)input_buf;
+
+    if (dpdk_pci_io_input->access_size != 1 &&
+        dpdk_pci_io_input->access_size != 2 &&
+        dpdk_pci_io_input->access_size != 4 &&
+        dpdk_pci_io_input->access_size != 8) {
+        status = STATUS_INVALID_PARAMETER;
+        goto end;
+    }
+
+    // Retrieve output buffer
+    status = WdfRequestRetrieveOutputBuffer(Request, sizeof(UINT64), &output_buf, &output_buf_size);
+    if (!NT_SUCCESS(status)) {
+        goto end;
+    }
+    ASSERT(output_buf_size == OutputBufferLength);
+
+    if (dpdk_pci_io_input->op == PCI_IO_READ) {
+        *(UINT64 *)output_buf = 0;
+        bytes_returned = ctx->bus_interface.GetBusData(
+            ctx->bus_interface.Context,
+            PCI_WHICHSPACE_CONFIG,
+            output_buf,
+            dpdk_pci_io_input->offset,
+            dpdk_pci_io_input->access_size);
+    }
+    else if (dpdk_pci_io_input->op == PCI_IO_WRITE) {
+        // returns bytes written
+        bytes_returned = ctx->bus_interface.SetBusData(
+            ctx->bus_interface.Context,
+            PCI_WHICHSPACE_CONFIG,
+            (PVOID)&dpdk_pci_io_input->data,
+            dpdk_pci_io_input->offset,
+            dpdk_pci_io_input->access_size);
+    }
+    else {
+        status = STATUS_INVALID_PARAMETER;
+        goto end;
+    }
+
+    if (bytes_returned != dpdk_pci_io_input->access_size) {
+        status = STATUS_INVALID_PARAMETER;
+    }
+
+end:
+    WdfRequestCompleteWithInformation(Request, status, bytes_returned);
+
+    return;
+}
diff --git a/windows/netuio/netuio_queue.h b/windows/netuio/netuio_queue.h
new file mode 100644
index 000000000..29888537c
--- /dev/null
+++ b/windows/netuio/netuio_queue.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Microsoft Corporation.
+ */
+
+#ifndef NETUIO_QUEUE_H
+#define NETUIO_QUEUE_H
+
+VOID
+netuio_unmap_address_from_user_process(_In_ PNETUIO_CONTEXT_DATA netuio_contextdata);
+
+NTSTATUS
+netuio_queue_initialize(_In_ WDFDEVICE hDevice);
+
+/**
+ * Events from the IoQueue object
+ */
+EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL netuio_evt_IO_device_control;
+
+EVT_WDF_IO_IN_CALLER_CONTEXT netuio_evt_IO_in_caller_context;
+
+#endif // NETUIO_QUEUE_H
-- 
2.23.0.vfs.1.1.63.g5a5ad7f


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [dpdk-dev] [PATCH v4] windows/netuio: add Windows NetUIO kernel driver
  2020-09-19  2:52   ` [dpdk-dev] [PATCH v4] " Narcisa Ana Maria Vasile
@ 2020-09-22 21:25     ` Dmitry Kozlyuk
  2020-09-22 21:36     ` Ranjit Menon
  2020-10-01 22:55     ` [dpdk-dev] [PATCH v5] " Narcisa Ana Maria Vasile
  2 siblings, 0 replies; 16+ messages in thread
From: Dmitry Kozlyuk @ 2020-09-22 21:25 UTC (permalink / raw)
  To: Narcisa Ana Maria Vasile
  Cc: dev, thomas, ocardona, haramakr, pallavi.kadam, ranjit.menon,
	dmitrym, Narcisa Vasile, Harini Ramakrishnan

On Fri, 18 Sep 2020 19:52:33 -0700, Narcisa Ana Maria Vasile wrote:
> From: Narcisa Vasile <navasile@microsoft.com>
> 
> The Windows netuio kernel driver provides the DPDK userspace application
> with direct access to hardware, by mapping the HW registers in userspace
> and allowing read/write operations from/to the device
> configuration space.
> 
> Two IOCTLs are defined by the netuio interface:
>   * IOCTL_NETUIO_MAP_HW_INTO_USERMODE
>       - used for mapping the device registers into userspace
>   * IOCTL_NETUIO_PCI_CONFIG_IO
>       - used to read/write from/into the device configuration space
> 
> Note:
> Requests to map the device BARs into userspace need to be processed
> in the thread context of the process that initiated the mapping request.
> Otherwise, the BARs might end up mapped into an arbitrary process
> address space.
> EvtIoInCallerContext is used to ensure the requests are handled
> in the right user thread context. Other requests (PCI config)
> are sent back to the framework and processed by the EvtIoDeviceControl
> callback.
> 
> Cc: Harini Ramakrishnan <Harini.Ramakrishnan@microsoft.com>
> Cc: Omar Cardona <ocardona@microsoft.com>
> Cc: Dmitry Malloy <dmitrym@microsoft.com>
> Signed-off-by: Narcisa Vasile <navasile@microsoft.com>
> ---

Reviewed-by: Dmitry Kozlyuk <dmitry.kozliuk@gmail.com>

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [dpdk-dev] [PATCH v4] windows/netuio: add Windows NetUIO kernel driver
  2020-09-19  2:52   ` [dpdk-dev] [PATCH v4] " Narcisa Ana Maria Vasile
  2020-09-22 21:25     ` Dmitry Kozlyuk
@ 2020-09-22 21:36     ` Ranjit Menon
  2020-10-01 22:55     ` [dpdk-dev] [PATCH v5] " Narcisa Ana Maria Vasile
  2 siblings, 0 replies; 16+ messages in thread
From: Ranjit Menon @ 2020-09-22 21:36 UTC (permalink / raw)
  To: Narcisa Ana Maria Vasile, dev, thomas, ocardona, haramakr,
	pallavi.kadam, dmitry.kozliuk
  Cc: dmitrym, Narcisa Vasile, Harini Ramakrishnan


On 9/18/2020 7:52 PM, Narcisa Ana Maria Vasile wrote:
> From: Narcisa Vasile <navasile@microsoft.com>
>
> The Windows netuio kernel driver provides the DPDK userspace application
> with direct access to hardware, by mapping the HW registers in userspace
> and allowing read/write operations from/to the device
> configuration space.
>
> Two IOCTLs are defined by the netuio interface:
>    * IOCTL_NETUIO_MAP_HW_INTO_USERMODE
>        - used for mapping the device registers into userspace
>    * IOCTL_NETUIO_PCI_CONFIG_IO
>        - used to read/write from/into the device configuration space
>
> Note:
> Requests to map the device BARs into userspace need to be processed
> in the thread context of the process that initiated the mapping request.
> Otherwise, the BARs might end up mapped into an arbitrary process
> address space.
> EvtIoInCallerContext is used to ensure the requests are handled
> in the right user thread context. Other requests (PCI config)
> are sent back to the framework and processed by the EvtIoDeviceControl
> callback.
>
> Cc: Harini Ramakrishnan <Harini.Ramakrishnan@microsoft.com>
> Cc: Omar Cardona <ocardona@microsoft.com>
> Cc: Dmitry Malloy <dmitrym@microsoft.com>
> Signed-off-by: Narcisa Vasile <navasile@microsoft.com>
> ---
>
> v4:
> Fixed issues reported by DmitryK:
> * Fix whitespaces issues
> * Fix status not checked
> * Remove NULL checks for device and file context getters
> * Use %x for BDF
> * Add comment for BAR traversing
> * Remove complex calculations that were only needed in ASSERT
> * Remove netuio_evt_driver_context_cleanup as not needed
> * Check GetBusData/SetBusData returned bytes
> * General cleanup
>
>   windows/.gitattributes                |   4 +
>   windows/.gitignore                    |   2 +
>   windows/netuio/README.rst             |  58 +++++
>   windows/netuio/netuio.inf             |  77 ++++++
>   windows/netuio/netuio.sln             |  24 ++
>   windows/netuio/netuio.vcxproj         | 113 +++++++++
>   windows/netuio/netuio.vcxproj.filters |  54 +++++
>   windows/netuio/netuio_dev.c           | 273 +++++++++++++++++++++
>   windows/netuio/netuio_dev.h           |  66 +++++
>   windows/netuio/netuio_drv.c           | 131 ++++++++++
>   windows/netuio/netuio_drv.h           |  30 +++
>   windows/netuio/netuio_interface.h     |  88 +++++++
>   windows/netuio/netuio_queue.c         | 334 ++++++++++++++++++++++++++
>   windows/netuio/netuio_queue.h         |  21 ++
>   14 files changed, 1275 insertions(+)
>   create mode 100644 windows/.gitattributes
>   create mode 100644 windows/.gitignore
>   create mode 100644 windows/netuio/README.rst
>   create mode 100644 windows/netuio/netuio.inf
>   create mode 100644 windows/netuio/netuio.sln
>   create mode 100644 windows/netuio/netuio.vcxproj
>   create mode 100644 windows/netuio/netuio.vcxproj.filters
>   create mode 100644 windows/netuio/netuio_dev.c
>   create mode 100644 windows/netuio/netuio_dev.h
>   create mode 100644 windows/netuio/netuio_drv.c
>   create mode 100644 windows/netuio/netuio_drv.h
>   create mode 100644 windows/netuio/netuio_interface.h
>   create mode 100644 windows/netuio/netuio_queue.c
>   create mode 100644 windows/netuio/netuio_queue.h
<snip>
> diff --git a/windows/netuio/netuio_interface.h b/windows/netuio/netuio_interface.h
> new file mode 100644
> index 000000000..232b5bc2d
> --- /dev/null
> +++ b/windows/netuio/netuio_interface.h
> @@ -0,0 +1,88 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2020 Microsoft Corporation.
> + */
> +
> + /**
> +  * @file netuio kernel driver interface
> +  */
> +
> +#ifndef NETUIO_INTERFACE_H
> +#define NETUIO_INTERFACE_H
> +
> +/**
> + * All structures in this file are packed on an 8B boundary.
> + */
> +#pragma pack(push)
> +#pragma pack(8)
> +
> +/**
> + * Define an Interface Guid so that any app can find the device and talk to it.
> + */
> +DEFINE_GUID (GUID_DEVINTERFACE_netUIO, 0x08336f60,0x0679,0x4c6c,0x85,0xd2,0xae,0x7c,0xed,0x65,0xff,0xf7); // {08336f60-0679-4c6c-85d2-ae7ced65fff7}
> +
> +/**
> + * Device name definitions
> + */
> +#define NETUIO_DEVICE_SYMBOLIC_LINK_UNICODE    L"\\DosDevices\\netuio"
> +#define NETUIO_MAX_SYMLINK_LEN                 255
> +
> +/**
> + * IOCTL_NETUIO_MAP_HW_INTO_USERMODE is used for mapping the device registers
> + * into userspace. It returns the physical address, virtual address
> + * and the size of the memory region where the BARs were mapped.
> + */
> +#define IOCTL_NETUIO_MAP_HW_INTO_USERMODE CTL_CODE(FILE_DEVICE_NETWORK, 51, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
Not critical, but can we rename this IOCTL code to

IOCTL_NETUIO_MAP_HW_INTO_USERSPACE since that seems more appropriate and it is what the comment is referring to?

<snip>


ranjit m.


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [dpdk-dev] [PATCH v5] windows/netuio: add Windows NetUIO kernel driver
  2020-09-19  2:52   ` [dpdk-dev] [PATCH v4] " Narcisa Ana Maria Vasile
  2020-09-22 21:25     ` Dmitry Kozlyuk
  2020-09-22 21:36     ` Ranjit Menon
@ 2020-10-01 22:55     ` Narcisa Ana Maria Vasile
  2020-10-02 18:21       ` Ranjit Menon
  2020-10-02 18:33       ` Dmitry Kozlyuk
  2 siblings, 2 replies; 16+ messages in thread
From: Narcisa Ana Maria Vasile @ 2020-10-01 22:55 UTC (permalink / raw)
  To: dev, thomas, ocardona, haramakr, pallavi.kadam, dmitry.kozliuk
  Cc: ranjit.menon, dmitrym, Narcisa Vasile, Harini Ramakrishnan

From: Narcisa Vasile <navasile@microsoft.com>

The Windows netuio kernel driver provides the DPDK userspace application
with direct access to hardware, by mapping the HW registers in userspace
and allowing read/write operations from/to the device
configuration space.

Two IOCTLs are defined by the netuio interface:
  * IOCTL_NETUIO_MAP_HW_INTO_USERSPACE
      - used for mapping the device registers into userspace
  * IOCTL_NETUIO_PCI_CONFIG_IO
      - used to read/write from/into the device configuration space

Note:
Requests to map the device BARs into userspace need to be processed
in the thread context of the process that initiated the mapping request.
Otherwise, the BARs might end up mapped into an arbitrary process
address space.
EvtIoInCallerContext is used to ensure the requests are handled
in the right user thread context. Other requests (PCI config)
are sent back to the framework and processed by the EvtIoDeviceControl
callback.

Cc: Harini Ramakrishnan <Harini.Ramakrishnan@microsoft.com>
Cc: Omar Cardona <ocardona@microsoft.com>
Cc: Dmitry Malloy <dmitrym@microsoft.com>
Signed-off-by: Narcisa Vasile <navasile@microsoft.com>
---

v5:
* Changed the name of the IOCTL for clarity

 windows/.gitattributes                |   4 +
 windows/.gitignore                    |   2 +
 windows/netuio/README.rst             |  58 +++++
 windows/netuio/netuio.inf             |  77 ++++++
 windows/netuio/netuio.sln             |  24 ++
 windows/netuio/netuio.vcxproj         | 113 +++++++++
 windows/netuio/netuio.vcxproj.filters |  54 +++++
 windows/netuio/netuio_dev.c           | 273 +++++++++++++++++++++
 windows/netuio/netuio_dev.h           |  66 +++++
 windows/netuio/netuio_drv.c           | 131 ++++++++++
 windows/netuio/netuio_drv.h           |  30 +++
 windows/netuio/netuio_interface.h     |  88 +++++++
 windows/netuio/netuio_queue.c         | 334 ++++++++++++++++++++++++++
 windows/netuio/netuio_queue.h         |  21 ++
 14 files changed, 1275 insertions(+)
 create mode 100644 windows/.gitattributes
 create mode 100644 windows/.gitignore
 create mode 100644 windows/netuio/README.rst
 create mode 100644 windows/netuio/netuio.inf
 create mode 100644 windows/netuio/netuio.sln
 create mode 100644 windows/netuio/netuio.vcxproj
 create mode 100644 windows/netuio/netuio.vcxproj.filters
 create mode 100644 windows/netuio/netuio_dev.c
 create mode 100644 windows/netuio/netuio_dev.h
 create mode 100644 windows/netuio/netuio_drv.c
 create mode 100644 windows/netuio/netuio_drv.h
 create mode 100644 windows/netuio/netuio_interface.h
 create mode 100644 windows/netuio/netuio_queue.c
 create mode 100644 windows/netuio/netuio_queue.h

diff --git a/windows/.gitattributes b/windows/.gitattributes
new file mode 100644
index 000000000..13482db3d
--- /dev/null
+++ b/windows/.gitattributes
@@ -0,0 +1,4 @@
+* text=auto
+
+*.sln text eol=crlf
+*.vcxproj text eol=crlf
diff --git a/windows/.gitignore b/windows/.gitignore
new file mode 100644
index 000000000..543281e8f
--- /dev/null
+++ b/windows/.gitignore
@@ -0,0 +1,2 @@
+x64/
+.vs/
diff --git a/windows/netuio/README.rst b/windows/netuio/README.rst
new file mode 100644
index 000000000..d9c5e3c8d
--- /dev/null
+++ b/windows/netuio/README.rst
@@ -0,0 +1,58 @@
+..  SPDX-License-Identifier: BSD-3-Clause
+    Copyright(c) 2020 Microsoft Corporation.
+
+Compiling the NetUIO Driver from Source
+=======================================
+
+Operating System
+~~~~~~~~~~~~~~~~
+
+The NetUIO source has been validated against the following operating systems:
+
+* Windows Server 2016
+* Windows Server 2019
+
+Hardware Requirements
+~~~~~~~~~~~~~~~~~~~~~
+The NetUIO driver has been validated using the following network adapters on the Windows platform:
+
+*
+*
+
+Software Requirements
+~~~~~~~~~~~~~~~~~~~~~
+For a list of required software tools please see the Prerequisites section in windows/README.rst.
+
+Building the NetUIO Driver
+--------------------------
+
+Follow the steps below to build the NetUIO driver and install the driver for the network adapter.
+
+* Clone the dpdk-kmods repository: git clone git://dpdk.org/dpdk-kmods
+* Navigate to \dpdk-kmods\windows\netuio
+* Load netuio.sln in Microsoft Visual Studio 2017 or 2019
+* Choose Release as the configuration mode and Build the solution
+* The resultant output files can be found in x64\Release\netuio
+
+Installing netuio.sys Driver for development
+--------------------------------------------
+.. note::
+
+   In a development environment, NetUIO driver can be loaded by enabling test-signing.
+   Please implement adequate precautionary measures before installing a test-signed driver, replacing a signed-driver.
+
+To ensure test-signed kernel-mode drivers can load on Windows, enable test-signing, using the following BCDEdit command.
+
+C:\windows\system32>Bcdedit.exe -set TESTSIGNING ON
+
+Windows displays the text "Test Mode" to remind users the system has test-signing enabled.
+Refer to the MSDN documentation on how to Test-Sign a Driver Package.
+
+To procure a WHQL signed NetUIO driver for Windows, please reach out to dpdkwin@microsoft.com
+
+* Go to Device Manager -> Network Adapters.
+* Right Click on target network adapter -> Select Update Driver.
+* Select "Browse my computer for driver software".
+* In the resultant window, select "Let me pick from a list of available drivers on my computer".
+* Select "DPDK netUIO for Network Adapter" from the list of drivers.
+* The NetUIO.sys driver is successfully installed.
diff --git a/windows/netuio/netuio.inf b/windows/netuio/netuio.inf
new file mode 100644
index 000000000..7121a0fa0
--- /dev/null
+++ b/windows/netuio/netuio.inf
@@ -0,0 +1,77 @@
+; SPDX-License-Identifier: BSD-3-Clause
+; Copyright (c) Microsoft Corporation. All rights reserved
+;
+; netuio.inf
+;
+
+[Version]
+Signature="$WINDOWS NT$"
+Class=%ClassName%
+ClassGuid={78912BC1-CB8E-4B28-A329-F322EBADBE0F}
+Provider=%Provider%
+CatalogFile=netuio.cat
+DriverVer=
+
+[ClassInstall32]
+Addreg=netuioClassReg
+
+[netuioClassReg]
+HKR,,,0,%ClassName%
+HKR,,Icon,,-5
+
+;*****************************************
+; Install Section
+;*****************************************
+
+[Manufacturer]
+%ManufacturerName%=Standard, NT$ARCH$
+
+[Standard.NT$ARCH$]
+%netuio.DeviceDesc%=netuio_Device, Root\netuio
+
+[netuio_Device.NT]
+CopyFiles=Drivers_Dir
+
+[Drivers_Dir]
+netuio.sys
+
+[netuio_Device.NT.HW]
+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)"
+
+;-------------- Service installation
+[netuio_Device.NT.Services]
+AddService = netuio,%SPSVCINST_ASSOCSERVICE%, netuio_Service_Inst
+
+; -------------- netuio driver install sections
+[netuio_Service_Inst]
+DisplayName    = %netuio.SVCDESC%
+ServiceType    = 1               ; SERVICE_KERNEL_DRIVER
+StartType      = 3               ; SERVICE_DEMAND_START
+ErrorControl   = 1               ; SERVICE_ERROR_NORMAL
+ServiceBinary  = %12%\netuio.sys
+AddReg         = DMAr.reg
+
+[DestinationDirs]
+DefaultDestDir = 12
+
+[SourceDisksNames]
+1 = %DiskName%,,,""
+
+[SourceDisksFiles]
+netuio.sys  = 1,,
+
+[Strings]
+SPSVCINST_ASSOCSERVICE= 0x00000002
+ManufacturerName = "Microsoft"
+Provider = "Vendor"
+ClassName = "Windows UIO"
+DiskName = "DPDK netUIO Installation Disk"
+netuio.DeviceDesc = "netuio Device"
+netuio.SVCDESC = "netuio Service"
+
+[DMAr.reg]
+HKR,Parameters,DmaRemappingCompatible,0x00010001,1
diff --git a/windows/netuio/netuio.sln b/windows/netuio/netuio.sln
new file mode 100644
index 000000000..15c26e6b9
--- /dev/null
+++ b/windows/netuio/netuio.sln
@@ -0,0 +1,24 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.25420.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "netuio", "netuio.vcxproj", "{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|x64 = Debug|x64
+		Release|x64 = Release|x64
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}.Debug|x64.ActiveCfg = Debug|x64
+		{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}.Debug|x64.Build.0 = Debug|x64
+		{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}.Debug|x64.Deploy.0 = Debug|x64
+		{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}.Release|x64.ActiveCfg = Release|x64
+		{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}.Release|x64.Build.0 = Release|x64
+		{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}.Release|x64.Deploy.0 = Release|x64
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal
diff --git a/windows/netuio/netuio.vcxproj b/windows/netuio/netuio.vcxproj
new file mode 100644
index 000000000..87c8b940c
--- /dev/null
+++ b/windows/netuio/netuio.vcxproj
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>{66EC91EF-AC5B-4D1E-8314-9B3E2855CCF6}</ProjectGuid>
+    <TemplateGuid>{497e31cb-056b-4f31-abb8-447fd55ee5a5}</TemplateGuid>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <MinimumVisualStudioVersion>12.0</MinimumVisualStudioVersion>
+    <Configuration>Debug</Configuration>
+    <Platform Condition="'$(Platform)' == ''">Win32</Platform>
+    <RootNamespace>netuio</RootNamespace>
+    <WindowsTargetPlatformVersion>$(LatestTargetPlatformVersion)</WindowsTargetPlatformVersion>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <TargetVersion>
+    </TargetVersion>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
+    <ConfigurationType>Driver</ConfigurationType>
+    <DriverType>KMDF</DriverType>
+    <DriverTargetPlatform>Desktop</DriverTargetPlatform>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <TargetVersion>
+    </TargetVersion>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
+    <ConfigurationType>Driver</ConfigurationType>
+    <DriverType>KMDF</DriverType>
+    <DriverTargetPlatform>Desktop</DriverTargetPlatform>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
+    <OutDir>$(SolutionDir)$(Platform)\$(ConfigurationName)\$(MSBuildProjectName)\</OutDir>
+    <IntDir>$(SolutionDir)$(Platform)\$(ConfigurationName)\$(MSBuildProjectName)\</IntDir>
+    <Inf2CatUseLocalTime>true</Inf2CatUseLocalTime>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <DebuggerFlavor>DbgengKernelDebugger</DebuggerFlavor>
+    <OutDir>$(SolutionDir)$(Platform)\$(ConfigurationName)\$(MSBuildProjectName)\</OutDir>
+    <IntDir>$(SolutionDir)$(Platform)\$(ConfigurationName)\$(MSBuildProjectName)\</IntDir>
+    <Inf2CatUseLocalTime>true</Inf2CatUseLocalTime>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <WppEnabled>false</WppEnabled>
+      <WppRecorderEnabled>true</WppRecorderEnabled>
+      <WppScanConfigurationData Condition="'%(ClCompile.ScanConfigurationData)' == ''">trace.h</WppScanConfigurationData>
+      <WppKernelMode>true</WppKernelMode>
+      <WppMinimalRebuildFromTracking>false</WppMinimalRebuildFromTracking>
+    </ClCompile>
+    <Inf>
+      <TimeStamp>0.6.1</TimeStamp>
+    </Inf>
+    <Link>
+      <AdditionalDependencies>%(AdditionalDependencies);$(KernelBufferOverflowLib);$(DDK_LIB_PATH)ntoskrnl.lib;$(DDK_LIB_PATH)hal.lib;$(DDK_LIB_PATH)wmilib.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfLdr.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfDriverEntry.lib;$(DDK_LIB_PATH)\wdmsec.lib</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <WppEnabled>false</WppEnabled>
+      <WppRecorderEnabled>true</WppRecorderEnabled>
+      <WppScanConfigurationData Condition="'%(ClCompile.ScanConfigurationData)' == ''">trace.h</WppScanConfigurationData>
+      <WppKernelMode>true</WppKernelMode>
+      <WppMinimalRebuildFromTracking>false</WppMinimalRebuildFromTracking>
+    </ClCompile>
+    <Inf>
+      <TimeStamp>0.6.1</TimeStamp>
+    </Inf>
+    <Link>
+      <AdditionalDependencies>%(AdditionalDependencies);$(KernelBufferOverflowLib);$(DDK_LIB_PATH)ntoskrnl.lib;$(DDK_LIB_PATH)hal.lib;$(DDK_LIB_PATH)wmilib.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfLdr.lib;$(KMDF_LIB_PATH)$(KMDF_VER_PATH)\WdfDriverEntry.lib;$(DDK_LIB_PATH)\wdmsec.lib</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <FilesToPackage Include="$(TargetPath)" />
+  </ItemGroup>
+  <ItemGroup>
+    <Inf Include="netuio.inf" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="netuio_dev.h" />
+    <ClInclude Include="netuio_drv.h" />
+    <ClInclude Include="netuio_interface.h" />
+    <ClInclude Include="netuio_queue.h" />
+    <ClInclude Include="resource.h" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="netuio_dev.c" />
+    <ClCompile Include="netuio_drv.c" />
+    <ClCompile Include="netuio_queue.c" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>
\ No newline at end of file
diff --git a/windows/netuio/netuio.vcxproj.filters b/windows/netuio/netuio.vcxproj.filters
new file mode 100644
index 000000000..49c1da15b
--- /dev/null
+++ b/windows/netuio/netuio.vcxproj.filters
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <Filter Include="Source Files">
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+    </Filter>
+    <Filter Include="Header Files">
+      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+      <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
+    </Filter>
+    <Filter Include="Resource Files">
+      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+    </Filter>
+    <Filter Include="Driver Files">
+      <UniqueIdentifier>{8E41214B-6785-4CFE-B992-037D68949A14}</UniqueIdentifier>
+      <Extensions>inf;inv;inx;mof;mc;</Extensions>
+    </Filter>
+  </ItemGroup>
+  <ItemGroup>
+    <Inf Include="netuio.inf">
+      <Filter>Driver Files</Filter>
+    </Inf>
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="netuio_interface.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="netuio_dev.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="netuio_drv.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="netuio_queue.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="resource.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="netuio_queue.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="netuio_dev.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="netuio_drv.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+  </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/windows/netuio/netuio_dev.c b/windows/netuio/netuio_dev.c
new file mode 100644
index 000000000..b2deb10c5
--- /dev/null
+++ b/windows/netuio/netuio_dev.c
@@ -0,0 +1,273 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Microsoft Corporation.
+ */
+
+#include <stdio.h>
+#include "netuio_drv.h"
+
+#include <wdmguid.h>
+#include <ntstrsafe.h>
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text (PAGE, netuio_create_device)
+#pragma alloc_text (PAGE, netuio_map_hw_resources)
+#pragma alloc_text (PAGE, netuio_free_hw_resources)
+#endif
+
+static NTSTATUS
+get_pci_device_info(_In_ WDFOBJECT device)
+{
+    NTSTATUS status = STATUS_UNSUCCESSFUL;
+
+    PNETUIO_CONTEXT_DATA  ctx;
+    ctx = netuio_get_context_data(device);
+
+    ctx->wdf_device = device;  // Store for later use
+
+    // Obtain the BUS_INTERFACE_STANDARD interface from the Bus Driver
+    status = WdfFdoQueryForInterface(device,
+                                     &GUID_BUS_INTERFACE_STANDARD,
+                                     (PINTERFACE)&ctx->bus_interface,
+                                     sizeof(BUS_INTERFACE_STANDARD),
+                                     1,
+                                     NULL);
+    if (!NT_SUCCESS(status))
+        return status;
+
+    // Retrieve the B:D:F details of our device
+    PDEVICE_OBJECT pdo = NULL;
+    pdo = WdfDeviceWdmGetPhysicalDevice(device);
+    if (pdo) {
+        ULONG prop = 0, length = 0;
+        status = IoGetDeviceProperty(pdo, DevicePropertyBusNumber, sizeof(ULONG), (PVOID)&ctx->addr.bus_num, &length);
+        if (!NT_SUCCESS(status)) {
+            return status;
+        }
+
+        status = IoGetDeviceProperty(pdo, DevicePropertyAddress, sizeof(ULONG), (PVOID)&prop, &length);
+        if (!NT_SUCCESS(status)) {
+            return status;
+        }
+
+        ctx->addr.func_num = prop & 0x0000FFFF;
+        ctx->addr.dev_num = ((prop >> 16) & 0x0000FFFF);
+    }
+
+    return status;
+}
+
+static NTSTATUS
+create_device_specific_symbolic_link(_In_ WDFOBJECT device)
+{
+    DECLARE_UNICODE_STRING_SIZE(symbolic_link, NETUIO_MAX_SYMLINK_LEN);
+
+    PNETUIO_CONTEXT_DATA  ctx;
+    ctx = netuio_get_context_data(device);
+
+    // Build symbolic link name as <netuio_symbolic_link>_BDF  (bus/device/func)
+    RtlUnicodeStringPrintf(&symbolic_link,
+                           L"%s_%04x%02x%02x",
+                           NETUIO_DEVICE_SYMBOLIC_LINK_UNICODE,
+                           ctx->addr.bus_num,
+                           ctx->addr.dev_num,
+                           ctx->addr.func_num);
+
+    return WdfDeviceCreateSymbolicLink(device, &symbolic_link);
+}
+
+/*
+Routine Description:
+    Worker routine called to create a device and its software resources.
+
+Return Value:
+    NTSTATUS
+ */
+_Use_decl_annotations_
+NTSTATUS
+netuio_create_device(PWDFDEVICE_INIT DeviceInit)
+{
+    NTSTATUS status;
+    WDFDEVICE device = NULL;
+
+    WDF_OBJECT_ATTRIBUTES deviceAttributes;
+    WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;
+    WDF_FILEOBJECT_CONFIG fileConfig;
+    WDF_OBJECT_ATTRIBUTES fileAttributes;
+
+    PAGED_CODE();
+
+    // Register PnP power management callbacks
+    WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
+    pnpPowerCallbacks.EvtDevicePrepareHardware = netuio_evt_prepare_hw;
+    pnpPowerCallbacks.EvtDeviceReleaseHardware = netuio_evt_release_hw;
+    WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);
+
+    // Register callbacks for when a HANDLE is opened or closed.
+    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&fileAttributes, NETUIO_FILE_CONTEXT_DATA);
+    WDF_FILEOBJECT_CONFIG_INIT(&fileConfig, WDF_NO_EVENT_CALLBACK, WDF_NO_EVENT_CALLBACK, netuio_evt_file_cleanup);
+    WdfDeviceInitSetFileObjectConfig(DeviceInit, &fileConfig, &fileAttributes);
+
+    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, NETUIO_CONTEXT_DATA);
+    WdfDeviceInitSetIoInCallerContextCallback(DeviceInit, netuio_evt_IO_in_caller_context);
+
+    status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);
+
+    if (NT_SUCCESS(status)) {
+        // Create a device interface so that applications can find and talk to us.
+        status = WdfDeviceCreateDeviceInterface(device, &GUID_DEVINTERFACE_netUIO, NULL);
+    }
+
+    if (NT_SUCCESS(status)) {
+        // Retrieve and store PCI information
+        status = get_pci_device_info(device);
+    }
+
+    if (NT_SUCCESS(status)) {
+        // Create a symbolic link name for user-space access
+        status = create_device_specific_symbolic_link(device);
+    }
+
+    if (NT_SUCCESS(status)) {
+        // Initialize the I/O Package and any Queues
+        status = netuio_queue_initialize(device);
+    }
+
+    return status;
+}
+
+_Use_decl_annotations_
+NTSTATUS
+netuio_map_hw_resources(WDFDEVICE Device, WDFCMRESLIST Resources, WDFCMRESLIST ResourcesTranslated)
+{
+    UNREFERENCED_PARAMETER(Resources);
+
+    NTSTATUS status;
+
+    PNETUIO_CONTEXT_DATA  ctx;
+    ctx = netuio_get_context_data(Device);
+
+    PCI_COMMON_HEADER pci_config = {0};
+    ULONG bytes_returned;
+
+    // Read PCI configuration space
+    bytes_returned = ctx->bus_interface.GetBusData(
+        ctx->bus_interface.Context,
+        PCI_WHICHSPACE_CONFIG,
+        &pci_config,
+        0,
+        sizeof(pci_config));
+
+    if (bytes_returned != sizeof(pci_config)) {
+        status = STATUS_NOT_SUPPORTED;
+        goto end;
+    }
+
+    // Device type is implictly enforced by .inf
+    ASSERT(PCI_CONFIGURATION_TYPE(&pci_config) == PCI_DEVICE_TYPE);
+
+    RtlZeroMemory(ctx->bar, sizeof(ctx->bar));
+    RtlZeroMemory(ctx->dpdk_hw, sizeof(ctx->dpdk_hw));
+
+    PCM_PARTIAL_RESOURCE_DESCRIPTOR descriptor;
+    ULONG next_descriptor = 0;
+    ULONG curr_bar = 0;
+    ULONG prev_bar = 0;
+
+   /*
+    * ResourcesTranslated report MMIO BARs in the correct order, but their
+    * indices differ from physical ones.
+
+    * For example:
+    * The values returned by WdfCmResourceListGetDescriptor can be condensed like this:
+    * Bar[0] = c840000c
+    * Bar[1] = c800000c
+    * Bar[2] = c844000c
+
+    * But the BAR array has to be filled like this :
+    * Bar[0] = c840000c
+    * Bar[1] = null
+    * Bar[2] = c800000c
+    * Bar[3] = null
+    * Bar[4] = c844000c
+    * Bar[5] = null
+
+    * Traverse PCI configuration manually to maintain physical index,
+    * searching for the next MMIO resource each time.
+    */
+    for (INT bar_index = 0; bar_index < PCI_MAX_BAR; bar_index++) {
+        prev_bar = curr_bar;
+        curr_bar = pci_config.u.type0.BaseAddresses[bar_index];
+        if (curr_bar == 0 || (prev_bar & PCI_TYPE_64BIT)) {
+            continue;
+        }
+
+        // Find next CmResourceTypeMemory
+        do {
+            descriptor = WdfCmResourceListGetDescriptor(ResourcesTranslated, next_descriptor);
+            next_descriptor++;
+
+            if (descriptor == NULL) {
+                status = STATUS_DEVICE_CONFIGURATION_ERROR;
+                goto end;
+            }
+        } while ((descriptor->Type != CmResourceTypeMemory) ||
+                 !(descriptor->Flags & CM_RESOURCE_MEMORY_BAR));
+
+        // Retrieve and map the BARs
+        ctx->bar[bar_index].base_addr.QuadPart = descriptor->u.Memory.Start.QuadPart;
+        ctx->bar[bar_index].size = descriptor->u.Memory.Length;
+        ctx->bar[bar_index].virt_addr = MmMapIoSpace(descriptor->u.Memory.Start,
+                                                     descriptor->u.Memory.Length,
+                                                     MmNonCached);
+        if (ctx->bar[bar_index].virt_addr == NULL) {
+            status = STATUS_INSUFFICIENT_RESOURCES;
+            goto end;
+        }
+
+        // Allocate an MDL for the device BAR, so we can map it to the user's process context later.
+        ctx->dpdk_hw[bar_index].mdl = IoAllocateMdl(ctx->bar[bar_index].virt_addr,
+                                                    (ULONG)ctx->bar[bar_index].size,
+                                                    FALSE,
+                                                    FALSE,
+                                                    NULL);
+        if (!ctx->dpdk_hw[bar_index].mdl) {
+            status = STATUS_INSUFFICIENT_RESOURCES;
+            goto end;
+        }
+
+        ctx->dpdk_hw[bar_index].mem.size = ctx->bar[bar_index].size;
+    } // for bar_index
+
+    status = STATUS_SUCCESS;
+
+end:
+    if (status != STATUS_SUCCESS) {
+        netuio_free_hw_resources(Device);
+    }
+
+    return status;
+}
+
+_Use_decl_annotations_
+VOID
+netuio_free_hw_resources(WDFDEVICE Device)
+{
+    PNETUIO_CONTEXT_DATA  ctx;
+    ctx = netuio_get_context_data(Device);
+
+    for (UINT8 bar_index = 0; bar_index < PCI_MAX_BAR; bar_index++) {
+
+        // Free the allocated MDLs
+        if (ctx->dpdk_hw[bar_index].mdl) {
+            IoFreeMdl(ctx->dpdk_hw[bar_index].mdl);
+        }
+
+        // Unmap all the BAR regions previously mapped
+        if (ctx->bar[bar_index].virt_addr) {
+            MmUnmapIoSpace(ctx->bar[bar_index].virt_addr, ctx->bar[bar_index].size);
+        }
+    }
+
+    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
new file mode 100644
index 000000000..62851141d
--- /dev/null
+++ b/windows/netuio/netuio_dev.h
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Microsoft Corporation.
+ */
+
+#ifndef NETUIO_DEV_H
+#define NETUIO_DEV_H
+
+#include "netuio_interface.h"
+
+struct pci_bar {
+    PHYSICAL_ADDRESS base_addr;
+    PVOID            virt_addr;
+    UINT64           size;
+};
+
+struct mem_map_region {
+    PMDL               mdl;    /**< MDL describing the memory region */
+    struct mem_region  mem;    /**< Memory region details */
+};
+
+struct dev_addr {
+    ULONG   bus_num;
+    USHORT  dev_num;
+    USHORT  func_num;
+};
+
+/**
+ * The device context performs the same job as a WDM device extension in the driver frameworks
+ */
+typedef struct _NETUIO_CONTEXT_DATA
+{
+    WDFDEVICE               wdf_device;             /**< WDF device handle to the FDO */
+    BUS_INTERFACE_STANDARD  bus_interface;          /**< Bus interface for config space access */
+    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 */
+} NETUIO_CONTEXT_DATA, *PNETUIO_CONTEXT_DATA;
+
+
+/**
+ * This macro will generate an inline function called DeviceGetContext
+ * which will be used to get a pointer to the device context memory in a
+ * type safe manner.
+ */
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(NETUIO_CONTEXT_DATA, netuio_get_context_data)
+
+typedef struct
+{
+    BOOLEAN bMapped;     /**< value is set to TRUE if the User-mode mapping was done for this file object. */
+}  NETUIO_FILE_CONTEXT_DATA, * PNETUIO_FILE_CONTEXT_DATA;
+
+
+/**
+ * This macro will generate an inline function which will be used to get
+ * a pointer to the file object's context memory in a type safe manner.
+ */
+WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(NETUIO_FILE_CONTEXT_DATA, netuio_get_file_object_context_data)
+
+/**
+ * Functions to initialize the device and its callbacks
+ */
+NTSTATUS netuio_create_device(_Inout_ PWDFDEVICE_INIT DeviceInit);
+NTSTATUS netuio_map_hw_resources(_In_ WDFDEVICE Device, _In_ WDFCMRESLIST Resources, _In_ WDFCMRESLIST ResourcesTranslated);
+VOID netuio_free_hw_resources(_In_ WDFDEVICE Device);
+
+#endif // NETUIO_DEV_H
diff --git a/windows/netuio/netuio_drv.c b/windows/netuio/netuio_drv.c
new file mode 100644
index 000000000..0d4ea7fb4
--- /dev/null
+++ b/windows/netuio/netuio_drv.c
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Microsoft Corporation.
+ */
+
+#include "netuio_drv.h"
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text (INIT, DriverEntry)
+#pragma alloc_text (PAGE, netuio_evt_device_add)
+#endif
+
+/*
+Routine Description:
+    DriverEntry initializes the driver and is the first routine called by the
+    system after the driver is loaded. DriverEntry specifies the other entry
+    points in the function driver, such as EvtDevice and DriverUnload.
+
+Return Value:
+    STATUS_SUCCESS if successful,
+    STATUS_UNSUCCESSFUL otherwise.
+ */
+NTSTATUS
+DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath)
+{
+    WDF_DRIVER_CONFIG config;
+    NTSTATUS status;
+    WDF_OBJECT_ATTRIBUTES attributes;
+
+    WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
+    WDF_DRIVER_CONFIG_INIT(&config, netuio_evt_device_add);
+
+    status = WdfDriverCreate(DriverObject, RegistryPath,
+                             &attributes, &config,
+                             WDF_NO_HANDLE);
+
+    if (!NT_SUCCESS(status)) {
+        return status;
+    }
+
+    return status;
+}
+
+/*
+Routine Description:
+    netuio_evt_device_add is called by the framework in response to AddDevice
+    call from the PnP manager. We create and initialize a device object to
+    represent a new instance of the device.
+
+Return Value:
+    NTSTATUS
+ */
+_Use_decl_annotations_
+NTSTATUS
+netuio_evt_device_add(WDFDRIVER Driver, PWDFDEVICE_INIT DeviceInit)
+{
+    UNREFERENCED_PARAMETER(Driver);
+    return netuio_create_device(DeviceInit);
+}
+
+/*
+Routine Description :
+    Maps HW resources and retrieves the PCI BAR address(es) of the device
+
+Return Value :
+    STATUS_SUCCESS is successful.
+    STATUS_<ERROR> otherwise
+-*/
+_Use_decl_annotations_
+NTSTATUS
+netuio_evt_prepare_hw(WDFDEVICE Device, WDFCMRESLIST Resources, WDFCMRESLIST ResourcesTranslated)
+{
+    NTSTATUS status;
+
+    status = netuio_map_hw_resources(Device, Resources, ResourcesTranslated);
+    if (!NT_SUCCESS(status)) {
+        return status;
+    }
+
+    PNETUIO_CONTEXT_DATA  ctx = netuio_get_context_data(Device);
+    DbgPrintEx(DPFLTR_IHVNETWORK_ID, DPFLTR_NETUIO_INFO_LEVEL, "netUIO Driver loaded...on device (B:D:F) %04d:%02d:%02d\n",
+        ctx->addr.bus_num, ctx->addr.dev_num, ctx->addr.func_num);
+
+    return status;
+}
+
+/*
+Routine Description :
+    Releases the resource mapped by netuio_evt_prepare_hw
+
+Return Value :
+    STATUS_SUCCESS always.
+-*/
+_Use_decl_annotations_
+NTSTATUS
+netuio_evt_release_hw(WDFDEVICE Device, WDFCMRESLIST ResourcesTranslated)
+{
+    UNREFERENCED_PARAMETER(ResourcesTranslated);
+
+    netuio_free_hw_resources(Device);
+
+    return STATUS_SUCCESS;
+}
+
+/*
+Routine Description :
+    EVT_WDF_FILE_CLEANUP callback, called when user process handle is closed.
+
+    Undoes IOCTL_NETUIO_MAP_HW_INTO_USERSPACE.
+
+Return value :
+    None
+-*/
+_Use_decl_annotations_
+VOID
+netuio_evt_file_cleanup(WDFFILEOBJECT FileObject)
+{
+    PNETUIO_FILE_CONTEXT_DATA file_ctx;
+
+    file_ctx = netuio_get_file_object_context_data(FileObject);
+
+    if (file_ctx->bMapped == TRUE)
+    {
+        WDFDEVICE device;
+        PNETUIO_CONTEXT_DATA ctx;
+
+        device = WdfFileObjectGetDevice(FileObject);
+        ctx = netuio_get_context_data(device);
+        netuio_unmap_address_from_user_process(ctx);
+        file_ctx->bMapped = FALSE;
+    }
+}
diff --git a/windows/netuio/netuio_drv.h b/windows/netuio/netuio_drv.h
new file mode 100644
index 000000000..469c6ac60
--- /dev/null
+++ b/windows/netuio/netuio_drv.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Microsoft Corporation.
+ */
+
+#ifndef NETUIO_DRV_H
+#define NETUIO_DRV_H
+
+#define INITGUID
+
+#include <ntddk.h>
+#include <wdf.h>
+
+#include "netuio_dev.h"
+#include "netuio_queue.h"
+
+/**
+ * Print output constants
+ */
+#define DPFLTR_NETUIO_INFO_LEVEL   35
+
+/**
+ * WDFDRIVER Events
+ */
+DRIVER_INITIALIZE DriverEntry;
+EVT_WDF_DRIVER_DEVICE_ADD       netuio_evt_device_add;
+EVT_WDF_DEVICE_PREPARE_HARDWARE netuio_evt_prepare_hw;
+EVT_WDF_DEVICE_RELEASE_HARDWARE netuio_evt_release_hw;
+EVT_WDF_FILE_CLOSE              netuio_evt_file_cleanup;
+
+#endif // NETUIO_DRV_H
diff --git a/windows/netuio/netuio_interface.h b/windows/netuio/netuio_interface.h
new file mode 100644
index 000000000..71f539537
--- /dev/null
+++ b/windows/netuio/netuio_interface.h
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Microsoft Corporation.
+ */
+
+ /**
+  * @file netuio kernel driver interface
+  */
+
+#ifndef NETUIO_INTERFACE_H
+#define NETUIO_INTERFACE_H
+
+/**
+ * All structures in this file are packed on an 8B boundary.
+ */
+#pragma pack(push)
+#pragma pack(8)
+
+/**
+ * Define an Interface Guid so that any app can find the device and talk to it.
+ */
+DEFINE_GUID (GUID_DEVINTERFACE_netUIO, 0x08336f60,0x0679,0x4c6c,0x85,0xd2,0xae,0x7c,0xed,0x65,0xff,0xf7); // {08336f60-0679-4c6c-85d2-ae7ced65fff7}
+
+/**
+ * Device name definitions
+ */
+#define NETUIO_DEVICE_SYMBOLIC_LINK_UNICODE    L"\\DosDevices\\netuio"
+#define NETUIO_MAX_SYMLINK_LEN                 255
+
+/**
+ * IOCTL_NETUIO_MAP_HW_INTO_USERSPACE is used for mapping the device registers
+ * into userspace. It returns the physical address, virtual address
+ * and the size of the memory region where the BARs were mapped.
+ */
+#define IOCTL_NETUIO_MAP_HW_INTO_USERSPACE CTL_CODE(FILE_DEVICE_NETWORK, 51, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
+
+/**
+ * IOCTL_NETUIO_PCI_CONFIG_IO is used to read/write from/into the device
+ * configuration space.
+ *
+ * Input:
+ *   - the operation type (read/write)
+ *   - the offset into the device data where the operation begins
+ *   - the length of data to read/write.
+ *   - in case of a write operation, the data to be written to the device
+ *     configuration space.
+ *
+ * Output:
+ *   - in case of a read operation, the output buffer is filled
+ *     with the data read from the configuration space.
+ */
+#define IOCTL_NETUIO_PCI_CONFIG_IO        CTL_CODE(FILE_DEVICE_NETWORK, 52, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
+
+struct mem_region {
+    UINT64           size;       /**< memory region size */
+    LARGE_INTEGER    phys_addr;  /**< physical address of the memory region */
+    PVOID            virt_addr;  /**< virtual address of the memory region */
+    PVOID            user_mapped_virt_addr;  /**< virtual address of the region mapped into user process context */
+};
+
+enum pci_io {
+    PCI_IO_READ = 0,
+    PCI_IO_WRITE = 1
+};
+
+#define PCI_MAX_BAR 6
+
+struct device_info
+{
+    struct mem_region   hw[PCI_MAX_BAR];
+};
+
+struct dpdk_pci_config_io
+{
+    UINT32              offset; /**< offset into the device config space where the reading/writing starts */
+    UINT8               op; /**< operation type: read or write */
+    UINT32              access_size; /**< 1, 2, 4, or 8 bytes */
+
+    union dpdk_pci_config_io_data {
+        UINT8			u8;
+        UINT16			u16;
+        UINT32			u32;
+        UINT64			u64;
+    } data; /**< Data to be written, in case of write operations */
+};
+
+#pragma pack(pop)
+
+#endif // NETUIO_INTERFACE_H
diff --git a/windows/netuio/netuio_queue.c b/windows/netuio/netuio_queue.c
new file mode 100644
index 000000000..058339404
--- /dev/null
+++ b/windows/netuio/netuio_queue.c
@@ -0,0 +1,334 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Microsoft Corporation.
+ */
+
+#include "netuio_drv.h"
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text (PAGE, netuio_queue_initialize)
+#endif
+
+static
+BOOLEAN
+netuio_get_usermode_mapping_flag(WDFREQUEST Request)
+{
+    WDFFILEOBJECT file_object;
+
+    file_object = WdfRequestGetFileObject(Request);
+    if (file_object == NULL)
+    {
+        goto end;
+    }
+
+    PNETUIO_FILE_CONTEXT_DATA file_ctx;
+
+    file_ctx = netuio_get_file_object_context_data(file_object);
+    if (file_ctx->bMapped)
+    {
+        return TRUE;
+    }
+
+end:
+    return FALSE;
+}
+
+static
+VOID
+netuio_set_usermode_mapping_flag(WDFREQUEST Request)
+{
+    WDFFILEOBJECT file_object;
+
+    file_object = WdfRequestGetFileObject(Request);
+    if (file_object == NULL)
+    {
+        return;
+    }
+
+    PNETUIO_FILE_CONTEXT_DATA file_ctx;
+
+    file_ctx = netuio_get_file_object_context_data(file_object);
+
+    file_ctx->bMapped = TRUE;
+}
+
+static void
+netuio_handle_get_hw_data_request(_In_ PNETUIO_CONTEXT_DATA ctx,
+                                  _In_ PVOID outputBuf, _In_ size_t outputBufSize)
+{
+    ASSERT(outputBufSize == sizeof(struct device_info));
+
+    struct device_info *dpdk_pvt_info = (struct device_info *)outputBuf;
+    RtlZeroMemory(dpdk_pvt_info, outputBufSize);
+
+    for (ULONG idx = 0; idx < PCI_MAX_BAR; idx++) {
+        dpdk_pvt_info->hw[idx].phys_addr.QuadPart = ctx->bar[idx].base_addr.QuadPart;
+        dpdk_pvt_info->hw[idx].user_mapped_virt_addr = ctx->dpdk_hw[idx].mem.user_mapped_virt_addr;
+        dpdk_pvt_info->hw[idx].size = ctx->bar[idx].size;
+    }
+}
+
+/*
+Routine Description:
+    Maps address ranges into the usermode process's address space.  The following
+    ranges are mapped:
+
+        * Any PCI BARs that our device was assigned
+        * The scratch buffer of contiguous pages
+
+Return Value:
+    NTSTATUS
+*/
+static NTSTATUS
+netuio_map_address_into_user_process(_In_ PNETUIO_CONTEXT_DATA ctx, WDFREQUEST Request)
+{
+    NTSTATUS status = STATUS_SUCCESS;
+
+    if (netuio_get_usermode_mapping_flag(Request))
+    {
+        goto end;
+    }
+
+    // Map any device BAR(s) to the user's process context
+    for (INT idx = 0; idx < PCI_MAX_BAR; idx++) {
+        if (ctx->dpdk_hw[idx].mdl == NULL) {
+            continue;
+        }
+
+        MmBuildMdlForNonPagedPool(ctx->dpdk_hw[idx].mdl);
+        __try {
+            ctx->dpdk_hw[idx].mem.user_mapped_virt_addr =
+                MmMapLockedPagesSpecifyCache(ctx->dpdk_hw[idx].mdl, UserMode,
+                                             MmCached, NULL, FALSE, NormalPagePriority);
+
+            if (ctx->dpdk_hw[idx].mem.user_mapped_virt_addr == NULL) {
+                status = STATUS_INSUFFICIENT_RESOURCES;
+                goto end;
+            }
+        }
+        __except (EXCEPTION_EXECUTE_HANDLER) {
+            status = GetExceptionCode();
+            goto end;
+        }
+    }
+
+end:
+    if (status != STATUS_SUCCESS) {
+        netuio_unmap_address_from_user_process(ctx);
+    }
+
+    return status;
+}
+
+/*
+Routine Description:
+    Unmaps all address ranges from the usermode process address space.
+    MUST be called in the context of the same process which created
+    the mapping.
+
+Return Value:
+    None
+ */
+_Use_decl_annotations_
+VOID
+netuio_unmap_address_from_user_process(PNETUIO_CONTEXT_DATA ctx)
+{
+    for (INT idx = 0; idx < PCI_MAX_BAR; idx++) {
+        if (ctx->dpdk_hw[idx].mem.user_mapped_virt_addr != NULL) {
+            MmUnmapLockedPages(ctx->dpdk_hw[idx].mem.user_mapped_virt_addr,
+                               ctx->dpdk_hw[idx].mdl);
+
+            ctx->dpdk_hw[idx].mem.user_mapped_virt_addr = NULL;
+        }
+    }
+}
+
+/*
+Routine Description:
+    The I/O dispatch callbacks for the frameworks device object are configured here.
+    A single default I/O Queue is configured for parallel request processing, and a
+    driver context memory allocation is created to hold our structure QUEUE_CONTEXT.
+
+Return Value:
+    None
+ */
+_Use_decl_annotations_
+NTSTATUS
+netuio_queue_initialize(WDFDEVICE Device)
+{
+    WDFQUEUE queue;
+    NTSTATUS status;
+    WDF_IO_QUEUE_CONFIG    queueConfig;
+
+    PAGED_CODE();
+
+    // Configure a default queue so that requests that are not
+    // configure-fowarded using WdfDeviceConfigureRequestDispatching to goto
+    // other queues get dispatched here.
+    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig, WdfIoQueueDispatchParallel);
+
+    queueConfig.EvtIoDeviceControl = netuio_evt_IO_device_control;
+
+    status = WdfIoQueueCreate(Device,
+                              &queueConfig,
+                              WDF_NO_OBJECT_ATTRIBUTES,
+                              &queue);
+
+    if( !NT_SUCCESS(status) ) {
+        return status;
+    }
+
+    return status;
+}
+
+/*
+Routine Description:
+    This routine is invoked to preprocess an I/O request before being placed into a queue.
+    It is guaranteed that it executes in the context of the process that generated the request.
+
+Return Value:
+    None
+ */
+_Use_decl_annotations_
+VOID
+netuio_evt_IO_in_caller_context(WDFDEVICE  Device,
+                                WDFREQUEST Request)
+{
+    WDF_REQUEST_PARAMETERS params = { 0 };
+    NTSTATUS status = STATUS_SUCCESS;
+    PVOID    output_buf = NULL;
+    size_t   output_buf_size;
+    size_t  bytes_returned = 0;
+    PNETUIO_CONTEXT_DATA  ctx = NULL;
+
+    ctx = netuio_get_context_data(Device);
+
+    WDF_REQUEST_PARAMETERS_INIT(&params);
+    WdfRequestGetParameters(Request, &params);
+
+    if (params.Type != WdfRequestTypeDeviceControl)
+    {
+        status = STATUS_INVALID_DEVICE_REQUEST;
+        goto end;
+    }
+
+    // We only need to be in the context of the process that initiated the request
+    //when we need to map memory to userspace. Otherwise, send the request back to framework.
+    if (params.Parameters.DeviceIoControl.IoControlCode != IOCTL_NETUIO_MAP_HW_INTO_USERSPACE)
+    {
+        status = WdfDeviceEnqueueRequest(Device, Request);
+        if (!NT_SUCCESS(status))
+        {
+            goto end;
+        }
+        return;
+    }
+
+    // Return relevant data to the caller
+    status = WdfRequestRetrieveOutputBuffer(Request, sizeof(struct device_info), &output_buf, &output_buf_size);
+    if (!NT_SUCCESS(status)) {
+        goto end;
+    }
+
+    status = netuio_map_address_into_user_process(ctx, Request);
+    if (!NT_SUCCESS(status)) {
+        goto end;
+    }
+
+    netuio_set_usermode_mapping_flag(Request);
+
+    netuio_handle_get_hw_data_request(ctx, output_buf, output_buf_size);
+    bytes_returned = output_buf_size;
+
+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)
+{
+    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);
+
+    PNETUIO_CONTEXT_DATA  ctx;
+    ctx = netuio_get_context_data(device);
+
+    if (IoControlCode != IOCTL_NETUIO_PCI_CONFIG_IO)
+    {
+        status = STATUS_INVALID_DEVICE_REQUEST;
+        goto end;
+    }
+
+    // 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);
+    if (!NT_SUCCESS(status)) {
+        goto end;
+    }
+
+    struct dpdk_pci_config_io *dpdk_pci_io_input = (struct dpdk_pci_config_io *)input_buf;
+
+    if (dpdk_pci_io_input->access_size != 1 &&
+        dpdk_pci_io_input->access_size != 2 &&
+        dpdk_pci_io_input->access_size != 4 &&
+        dpdk_pci_io_input->access_size != 8) {
+        status = STATUS_INVALID_PARAMETER;
+        goto end;
+    }
+
+    // Retrieve output buffer
+    status = WdfRequestRetrieveOutputBuffer(Request, sizeof(UINT64), &output_buf, &output_buf_size);
+    if (!NT_SUCCESS(status)) {
+        goto end;
+    }
+    ASSERT(output_buf_size == OutputBufferLength);
+
+    if (dpdk_pci_io_input->op == PCI_IO_READ) {
+        *(UINT64 *)output_buf = 0;
+        bytes_returned = ctx->bus_interface.GetBusData(
+            ctx->bus_interface.Context,
+            PCI_WHICHSPACE_CONFIG,
+            output_buf,
+            dpdk_pci_io_input->offset,
+            dpdk_pci_io_input->access_size);
+    }
+    else if (dpdk_pci_io_input->op == PCI_IO_WRITE) {
+        // returns bytes written
+        bytes_returned = ctx->bus_interface.SetBusData(
+            ctx->bus_interface.Context,
+            PCI_WHICHSPACE_CONFIG,
+            (PVOID)&dpdk_pci_io_input->data,
+            dpdk_pci_io_input->offset,
+            dpdk_pci_io_input->access_size);
+    }
+    else {
+        status = STATUS_INVALID_PARAMETER;
+        goto end;
+    }
+
+    if (bytes_returned != dpdk_pci_io_input->access_size) {
+        status = STATUS_INVALID_PARAMETER;
+    }
+
+end:
+    WdfRequestCompleteWithInformation(Request, status, bytes_returned);
+
+    return;
+}
diff --git a/windows/netuio/netuio_queue.h b/windows/netuio/netuio_queue.h
new file mode 100644
index 000000000..29888537c
--- /dev/null
+++ b/windows/netuio/netuio_queue.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2020 Microsoft Corporation.
+ */
+
+#ifndef NETUIO_QUEUE_H
+#define NETUIO_QUEUE_H
+
+VOID
+netuio_unmap_address_from_user_process(_In_ PNETUIO_CONTEXT_DATA netuio_contextdata);
+
+NTSTATUS
+netuio_queue_initialize(_In_ WDFDEVICE hDevice);
+
+/**
+ * Events from the IoQueue object
+ */
+EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL netuio_evt_IO_device_control;
+
+EVT_WDF_IO_IN_CALLER_CONTEXT netuio_evt_IO_in_caller_context;
+
+#endif // NETUIO_QUEUE_H
-- 
2.23.0.vfs.1.1.63.g5a5ad7f


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [dpdk-dev] [PATCH v5] windows/netuio: add Windows NetUIO kernel driver
  2020-10-01 22:55     ` [dpdk-dev] [PATCH v5] " Narcisa Ana Maria Vasile
@ 2020-10-02 18:21       ` Ranjit Menon
  2020-10-05 19:34         ` Narcisa Ana Maria Vasile
  2020-10-14  9:29         ` Thomas Monjalon
  2020-10-02 18:33       ` Dmitry Kozlyuk
  1 sibling, 2 replies; 16+ messages in thread
From: Ranjit Menon @ 2020-10-02 18:21 UTC (permalink / raw)
  To: Narcisa Ana Maria Vasile, dev, thomas, ocardona, haramakr,
	pallavi.kadam, dmitry.kozliuk
  Cc: dmitrym, Narcisa Vasile, Harini Ramakrishnan


On 10/1/2020 3:55 PM, Narcisa Ana Maria Vasile wrote:
> From: Narcisa Vasile <navasile@microsoft.com>
>
> The Windows netuio kernel driver provides the DPDK userspace application
> with direct access to hardware, by mapping the HW registers in userspace
> and allowing read/write operations from/to the device
> configuration space.
>
> Two IOCTLs are defined by the netuio interface:
>    * IOCTL_NETUIO_MAP_HW_INTO_USERSPACE
>        - used for mapping the device registers into userspace
>    * IOCTL_NETUIO_PCI_CONFIG_IO
>        - used to read/write from/into the device configuration space
>
> Note:
> Requests to map the device BARs into userspace need to be processed
> in the thread context of the process that initiated the mapping request.
> Otherwise, the BARs might end up mapped into an arbitrary process
> address space.
> EvtIoInCallerContext is used to ensure the requests are handled
> in the right user thread context. Other requests (PCI config)
> are sent back to the framework and processed by the EvtIoDeviceControl
> callback.
>
> Cc: Harini Ramakrishnan <Harini.Ramakrishnan@microsoft.com>
> Cc: Omar Cardona <ocardona@microsoft.com>
> Cc: Dmitry Malloy <dmitrym@microsoft.com>
> Signed-off-by: Narcisa Vasile <navasile@microsoft.com>
> ---
>
> v5:
> * Changed the name of the IOCTL for clarity
>
>   windows/.gitattributes                |   4 +
>   windows/.gitignore                    |   2 +
>   windows/netuio/README.rst             |  58 +++++
>   windows/netuio/netuio.inf             |  77 ++++++
>   windows/netuio/netuio.sln             |  24 ++
>   windows/netuio/netuio.vcxproj         | 113 +++++++++
>   windows/netuio/netuio.vcxproj.filters |  54 +++++
>   windows/netuio/netuio_dev.c           | 273 +++++++++++++++++++++
>   windows/netuio/netuio_dev.h           |  66 +++++
>   windows/netuio/netuio_drv.c           | 131 ++++++++++
>   windows/netuio/netuio_drv.h           |  30 +++
>   windows/netuio/netuio_interface.h     |  88 +++++++
>   windows/netuio/netuio_queue.c         | 334 ++++++++++++++++++++++++++
>   windows/netuio/netuio_queue.h         |  21 ++
>   14 files changed, 1275 insertions(+)
>   create mode 100644 windows/.gitattributes
>   create mode 100644 windows/.gitignore
>   create mode 100644 windows/netuio/README.rst
>   create mode 100644 windows/netuio/netuio.inf
>   create mode 100644 windows/netuio/netuio.sln
>   create mode 100644 windows/netuio/netuio.vcxproj
>   create mode 100644 windows/netuio/netuio.vcxproj.filters
>   create mode 100644 windows/netuio/netuio_dev.c
>   create mode 100644 windows/netuio/netuio_dev.h
>   create mode 100644 windows/netuio/netuio_drv.c
>   create mode 100644 windows/netuio/netuio_drv.h
>   create mode 100644 windows/netuio/netuio_interface.h
>   create mode 100644 windows/netuio/netuio_queue.c
>   create mode 100644 windows/netuio/netuio_queue.h

We'll need to do a code-style cleanup at some point, but for now:

Reviewed-by: Ranjit Menon <ranjit.menon@intel.com>

Acked-by: Ranjit Menon <ranjit.menon@intel.com>


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [dpdk-dev] [PATCH v5] windows/netuio: add Windows NetUIO kernel driver
  2020-10-01 22:55     ` [dpdk-dev] [PATCH v5] " Narcisa Ana Maria Vasile
  2020-10-02 18:21       ` Ranjit Menon
@ 2020-10-02 18:33       ` Dmitry Kozlyuk
  1 sibling, 0 replies; 16+ messages in thread
From: Dmitry Kozlyuk @ 2020-10-02 18:33 UTC (permalink / raw)
  To: Narcisa Ana Maria Vasile
  Cc: dev, thomas, ocardona, haramakr, pallavi.kadam, ranjit.menon,
	dmitrym, Narcisa Vasile, Harini Ramakrishnan

On Thu,  1 Oct 2020 15:55:08 -0700, Narcisa Ana Maria Vasile wrote:
> From: Narcisa Vasile <navasile@microsoft.com>
> 
> The Windows netuio kernel driver provides the DPDK userspace application
> with direct access to hardware, by mapping the HW registers in userspace
> and allowing read/write operations from/to the device
> configuration space.
> 
> Two IOCTLs are defined by the netuio interface:
>   * IOCTL_NETUIO_MAP_HW_INTO_USERSPACE
>       - used for mapping the device registers into userspace
>   * IOCTL_NETUIO_PCI_CONFIG_IO
>       - used to read/write from/into the device configuration space
> 
> Note:
> Requests to map the device BARs into userspace need to be processed
> in the thread context of the process that initiated the mapping request.
> Otherwise, the BARs might end up mapped into an arbitrary process
> address space.
> EvtIoInCallerContext is used to ensure the requests are handled
> in the right user thread context. Other requests (PCI config)
> are sent back to the framework and processed by the EvtIoDeviceControl
> callback.
> 
> Cc: Harini Ramakrishnan <Harini.Ramakrishnan@microsoft.com>
> Cc: Omar Cardona <ocardona@microsoft.com>
> Cc: Dmitry Malloy <dmitrym@microsoft.com>
> Signed-off-by: Narcisa Vasile <navasile@microsoft.com>

Reviewed-by: Dmitry Kozlyuk <dmitry.kozliuk@gmail.com>

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [dpdk-dev] [PATCH v5] windows/netuio: add Windows NetUIO kernel driver
  2020-10-02 18:21       ` Ranjit Menon
@ 2020-10-05 19:34         ` Narcisa Ana Maria Vasile
  2020-10-14  9:29         ` Thomas Monjalon
  1 sibling, 0 replies; 16+ messages in thread
From: Narcisa Ana Maria Vasile @ 2020-10-05 19:34 UTC (permalink / raw)
  To: Ranjit Menon
  Cc: dev, thomas, ocardona, haramakr, pallavi.kadam, dmitry.kozliuk,
	dmitrym, Narcisa Vasile, Harini Ramakrishnan

On Fri, Oct 02, 2020 at 11:21:54AM -0700, Ranjit Menon wrote:
> 
> On 10/1/2020 3:55 PM, Narcisa Ana Maria Vasile wrote:
> >From: Narcisa Vasile <navasile@microsoft.com>
> >
> >The Windows netuio kernel driver provides the DPDK userspace application
> >with direct access to hardware, by mapping the HW registers in userspace
> >and allowing read/write operations from/to the device
> >configuration space.
> >
> >Two IOCTLs are defined by the netuio interface:
> >   * IOCTL_NETUIO_MAP_HW_INTO_USERSPACE
> >       - used for mapping the device registers into userspace
> >   * IOCTL_NETUIO_PCI_CONFIG_IO
> >       - used to read/write from/into the device configuration space
> >
> >Note:
> >Requests to map the device BARs into userspace need to be processed
> >in the thread context of the process that initiated the mapping request.
> >Otherwise, the BARs might end up mapped into an arbitrary process
> >address space.
> >EvtIoInCallerContext is used to ensure the requests are handled
> >in the right user thread context. Other requests (PCI config)
> >are sent back to the framework and processed by the EvtIoDeviceControl
> >callback.
> >
> >Cc: Harini Ramakrishnan <Harini.Ramakrishnan@microsoft.com>
> >Cc: Omar Cardona <ocardona@microsoft.com>
> >Cc: Dmitry Malloy <dmitrym@microsoft.com>
> >Signed-off-by: Narcisa Vasile <navasile@microsoft.com>
> >---
> >
> 
> We'll need to do a code-style cleanup at some point, but for now:
> 

Yes, agreed. I'll send a patch for style cleanup after merging this one.
Thanks, Ranjit.

> Reviewed-by: Ranjit Menon <ranjit.menon@intel.com>
> 
> Acked-by: Ranjit Menon <ranjit.menon@intel.com>

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [dpdk-dev] [PATCH v5] windows/netuio: add Windows NetUIO kernel driver
  2020-10-02 18:21       ` Ranjit Menon
  2020-10-05 19:34         ` Narcisa Ana Maria Vasile
@ 2020-10-14  9:29         ` Thomas Monjalon
  1 sibling, 0 replies; 16+ messages in thread
From: Thomas Monjalon @ 2020-10-14  9:29 UTC (permalink / raw)
  To: Narcisa Ana Maria Vasile
  Cc: dev, ocardona, haramakr, pallavi.kadam, dmitry.kozliuk, dmitrym,
	Harini Ramakrishnan, Ranjit Menon, Narcisa Vasile

02/10/2020 20:21, Ranjit Menon:
> On 10/1/2020 3:55 PM, Narcisa Ana Maria Vasile wrote:
> > From: Narcisa Vasile <navasile@microsoft.com>
> >
> > The Windows netuio kernel driver provides the DPDK userspace application
> > with direct access to hardware, by mapping the HW registers in userspace
> > and allowing read/write operations from/to the device
> > configuration space.
> >
> > Two IOCTLs are defined by the netuio interface:
> >    * IOCTL_NETUIO_MAP_HW_INTO_USERSPACE
> >        - used for mapping the device registers into userspace
> >    * IOCTL_NETUIO_PCI_CONFIG_IO
> >        - used to read/write from/into the device configuration space
> >
> > Note:
> > Requests to map the device BARs into userspace need to be processed
> > in the thread context of the process that initiated the mapping request.
> > Otherwise, the BARs might end up mapped into an arbitrary process
> > address space.
> > EvtIoInCallerContext is used to ensure the requests are handled
> > in the right user thread context. Other requests (PCI config)
> > are sent back to the framework and processed by the EvtIoDeviceControl
> > callback.
> >
> > Cc: Harini Ramakrishnan <Harini.Ramakrishnan@microsoft.com>
> > Cc: Omar Cardona <ocardona@microsoft.com>
> > Cc: Dmitry Malloy <dmitrym@microsoft.com>
> > Signed-off-by: Narcisa Vasile <navasile@microsoft.com>
> 
> We'll need to do a code-style cleanup at some point, but for now:
> 
> Reviewed-by: Ranjit Menon <ranjit.menon@intel.com>
> Acked-by: Ranjit Menon <ranjit.menon@intel.com>

For info, Reviewed-by is stronger because it means
you carefully checked the code.
I drop Acked-by redundancy in this case.

With Reviewed-by: Dmitry Kozlyuk <dmitry.kozliuk@gmail.com>

Applied, thanks

For next patches, I would prefer a prefix "kmods" in the subject:
	--subject-prefix='kmods PATCH'



^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2020-10-14  9:29 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-08-20 22:23 [dpdk-dev] [PATCH v2] windows/netuio: add Windows NetUIO kernel driver Narcisa Ana Maria Vasile
2020-08-21  1:32 ` Ranjit Menon
2020-09-09 18:58   ` Narcisa Ana Maria Vasile
2020-08-24 20:53 ` Dmitry Kozlyuk
2020-09-09 18:53   ` Narcisa Ana Maria Vasile
2020-09-13 21:39     ` Dmitry Kozlyuk
2020-09-09 18:41 ` [dpdk-dev] [PATCH v3] " Narcisa Ana Maria Vasile
2020-09-13 21:39   ` Dmitry Kozlyuk
2020-09-19  2:52   ` [dpdk-dev] [PATCH v4] " Narcisa Ana Maria Vasile
2020-09-22 21:25     ` Dmitry Kozlyuk
2020-09-22 21:36     ` Ranjit Menon
2020-10-01 22:55     ` [dpdk-dev] [PATCH v5] " Narcisa Ana Maria Vasile
2020-10-02 18:21       ` Ranjit Menon
2020-10-05 19:34         ` Narcisa Ana Maria Vasile
2020-10-14  9:29         ` Thomas Monjalon
2020-10-02 18:33       ` Dmitry Kozlyuk

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).