[NETKVM] Import NetKVM network adapter driver by Red Hat
[reactos.git] / drivers / network / dd / netkvm / Common / ParaNdis-Common.c
diff --git a/drivers/network/dd/netkvm/Common/ParaNdis-Common.c b/drivers/network/dd/netkvm/Common/ParaNdis-Common.c
new file mode 100644 (file)
index 0000000..ebb0c04
--- /dev/null
@@ -0,0 +1,3017 @@
+/*
+ * This file contains NDIS driver procedures, common for NDIS5 and NDIS6
+ *
+ * Copyright (c) 2008-2017 Red Hat, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met :
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and / or other materials provided with the distribution.
+ * 3. Neither the names of the copyright holders nor the names of their contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include "ndis56common.h"
+
+#ifdef WPP_EVENT_TRACING
+#include "ParaNdis-Common.tmh"
+#endif
+
+static void ReuseReceiveBufferRegular(PARANDIS_ADAPTER *pContext, pIONetDescriptor pBuffersDescriptor);
+static void ReuseReceiveBufferPowerOff(PARANDIS_ADAPTER *pContext, pIONetDescriptor pBuffersDescriptor);
+
+//#define ROUNDSIZE(sz) ((sz + 15) & ~15)
+#define MAX_VLAN_ID     4095
+
+#if 0
+void FORCEINLINE DebugDumpPacket(LPCSTR prefix, PVOID header, int level)
+{
+    PUCHAR peth = (PUCHAR)header;
+    DPrintf(level, ("[%s] %02X%02X%02X%02X%02X%02X => %02X%02X%02X%02X%02X%02X", prefix,
+        peth[6], peth[7], peth[8], peth[9], peth[10], peth[11],
+        peth[0], peth[1], peth[2], peth[3], peth[4], peth[5]));
+}
+#else
+void FORCEINLINE DebugDumpPacket(LPCSTR prefix, PVOID header, int level)
+{
+}
+#endif
+
+
+
+/**********************************************************
+Validates MAC address
+Valid MAC address is not broadcast, not multicast, not empty
+if bLocal is set, it must be LOCAL
+if not, is must be non-local or local
+Parameters:
+    PUCHAR pcMacAddress - MAC address to validate
+    BOOLEAN bLocal      - TRUE, if we validate locally administered address
+Return value:
+    TRUE if valid
+***********************************************************/
+BOOLEAN ParaNdis_ValidateMacAddress(PUCHAR pcMacAddress, BOOLEAN bLocal)
+{
+    BOOLEAN bLA = FALSE, bEmpty, bBroadcast, bMulticast = FALSE;
+    bBroadcast = ETH_IS_BROADCAST(pcMacAddress);
+    bLA = !bBroadcast && ETH_IS_LOCALLY_ADMINISTERED(pcMacAddress);
+    bMulticast = !bBroadcast && ETH_IS_MULTICAST(pcMacAddress);
+    bEmpty = ETH_IS_EMPTY(pcMacAddress);
+    return !bBroadcast && !bEmpty && !bMulticast && (!bLocal || bLA);
+}
+
+static eInspectedPacketType QueryPacketType(PVOID data)
+{
+    if (ETH_IS_BROADCAST(data))
+        return iptBroadcast;
+    if (ETH_IS_MULTICAST(data))
+        return iptMulticast;
+    return iptUnicast;
+}
+
+typedef struct _tagConfigurationEntry
+{
+    const char      *Name;
+    ULONG           ulValue;
+    ULONG           ulMinimal;
+    ULONG           ulMaximal;
+}tConfigurationEntry;
+
+typedef struct _tagConfigurationEntries
+{
+    tConfigurationEntry isPromiscuous;
+    tConfigurationEntry PrioritySupport;
+    tConfigurationEntry ConnectRate;
+    tConfigurationEntry isLogEnabled;
+    tConfigurationEntry debugLevel;
+    tConfigurationEntry connectTimer;
+    tConfigurationEntry dpcChecker;
+    tConfigurationEntry TxCapacity;
+    tConfigurationEntry RxCapacity;
+    tConfigurationEntry InterruptRecovery;
+    tConfigurationEntry LogStatistics;
+    tConfigurationEntry PacketFiltering;
+    tConfigurationEntry ScatterGather;
+    tConfigurationEntry BatchReceive;
+    tConfigurationEntry OffloadTxChecksum;
+    tConfigurationEntry OffloadTxLSO;
+    tConfigurationEntry OffloadRxCS;
+    tConfigurationEntry OffloadGuestCS;
+    tConfigurationEntry UseSwTxChecksum;
+    tConfigurationEntry IPPacketsCheck;
+    tConfigurationEntry stdIpcsV4;
+    tConfigurationEntry stdTcpcsV4;
+    tConfigurationEntry stdTcpcsV6;
+    tConfigurationEntry stdUdpcsV4;
+    tConfigurationEntry stdUdpcsV6;
+    tConfigurationEntry stdLsoV1;
+    tConfigurationEntry stdLsoV2ip4;
+    tConfigurationEntry stdLsoV2ip6;
+    tConfigurationEntry PriorityVlanTagging;
+    tConfigurationEntry VlanId;
+    tConfigurationEntry UseMergeableBuffers;
+    tConfigurationEntry MTU;
+    tConfigurationEntry NumberOfHandledRXPackersInDPC;
+    tConfigurationEntry Indirect;
+}tConfigurationEntries;
+
+static const tConfigurationEntries defaultConfiguration =
+{
+    { "Promiscuous",    0,  0,  1 },
+    { "Priority",       0,  0,  1 },
+    { "ConnectRate",    100,10,10000 },
+    { "DoLog",          1,  0,  1 },
+    { "DebugLevel",     2,  0,  8 },
+    { "ConnectTimer",   0,  0,  300000 },
+    { "DpcCheck",       0,  0,  2 },
+    { "TxCapacity",     1024,   16, 1024 },
+    { "RxCapacity",     256, 32, 1024 },
+    { "InterruptRecovery",  0, 0, 1},
+    { "LogStatistics",  0, 0, 10000},
+    { "PacketFilter",   1, 0, 1},
+    { "Gather",         1, 0, 1},
+    { "BatchReceive",   1, 0, 1},
+    { "Offload.TxChecksum", 0, 0, 31},
+    { "Offload.TxLSO",  0, 0, 2},
+    { "Offload.RxCS",   0, 0, 31},
+    { "Offload.GuestCS", 0, 0, 1},
+    { "UseSwTxChecksum", 0, 0, 1 },
+    { "IPPacketsCheck", 0, 0, 3 },
+    { "*IPChecksumOffloadIPv4", 3, 0, 3 },
+    { "*TCPChecksumOffloadIPv4",3, 0, 3 },
+    { "*TCPChecksumOffloadIPv6",3, 0, 3 },
+    { "*UDPChecksumOffloadIPv4",3, 0, 3 },
+    { "*UDPChecksumOffloadIPv6",3, 0, 3 },
+    { "*LsoV1IPv4", 1, 0, 1 },
+    { "*LsoV2IPv4", 1, 0, 1 },
+    { "*LsoV2IPv6", 1, 0, 1 },
+    { "*PriorityVLANTag", 3, 0, 3},
+    { "VlanId", 0, 0, MAX_VLAN_ID},
+    { "MergeableBuf", 1, 0, 1},
+    { "MTU", 1500, 500, 65500},
+    { "NumberOfHandledRXPackersInDPC", MAX_RX_LOOPS, 1, 10000},
+    { "Indirect", 0, 0, 2},
+};
+
+static void ParaNdis_ResetVirtIONetDevice(PARANDIS_ADAPTER *pContext)
+{
+    virtio_device_reset(&pContext->IODevice);
+    DPrintf(0, ("[%s] Done", __FUNCTION__));
+    /* reset all the features in the device */
+    pContext->ulCurrentVlansFilterSet = 0;
+    pContext->ullGuestFeatures = 0;
+#ifdef VIRTIO_RESET_VERIFY
+    if (1)
+    {
+        u8 devStatus;
+        devStatus = virtio_get_status(&pContext->IODevice);
+        if (devStatus)
+        {
+            DPrintf(0, ("[%s] Device status is still %02X", __FUNCTION__, (ULONG)devStatus));
+            virtio_device_reset(&pContext->IODevice);
+            devStatus = virtio_get_status(&pContext->IODevice);
+            DPrintf(0, ("[%s] Device status on retry %02X", __FUNCTION__, (ULONG)devStatus));
+        }
+    }
+#endif
+}
+
+/**********************************************************
+Gets integer value for specifies in pEntry->Name name
+Parameters:
+    NDIS_HANDLE cfg  previously open configuration
+    tConfigurationEntry *pEntry - Entry to fill value in
+***********************************************************/
+static void GetConfigurationEntry(NDIS_HANDLE cfg, tConfigurationEntry *pEntry)
+{
+    NDIS_STATUS status;
+    const char *statusName;
+    NDIS_STRING name = {0};
+    PNDIS_CONFIGURATION_PARAMETER pParam = NULL;
+    NDIS_PARAMETER_TYPE ParameterType = NdisParameterInteger;
+    NdisInitializeString(&name, (PUCHAR)pEntry->Name);
+    NdisReadConfiguration(
+        &status,
+        &pParam,
+        cfg,
+        &name,
+        ParameterType);
+    if (status == NDIS_STATUS_SUCCESS)
+    {
+        ULONG ulValue = pParam->ParameterData.IntegerData;
+        if (ulValue >= pEntry->ulMinimal && ulValue <= pEntry->ulMaximal)
+        {
+            pEntry->ulValue = ulValue;
+            statusName = "value";
+        }
+        else
+        {
+            statusName = "out of range";
+        }
+    }
+    else
+    {
+        statusName = "nothing";
+    }
+    DPrintf(2, ("[%s] %s read for %s - 0x%x",
+        __FUNCTION__,
+        statusName,
+        pEntry->Name,
+        pEntry->ulValue));
+    if (name.Buffer) NdisFreeString(name);
+}
+
+static void DisableLSOv4Permanently(PARANDIS_ADAPTER *pContext, LPCSTR procname, LPCSTR reason)
+{
+    if (pContext->Offload.flagsValue & osbT4Lso)
+    {
+        DPrintf(0, ("[%s] Warning: %s", procname, reason));
+        pContext->Offload.flagsValue &= ~osbT4Lso;
+        ParaNdis_ResetOffloadSettings(pContext, NULL, NULL);
+    }
+}
+
+static void DisableLSOv6Permanently(PARANDIS_ADAPTER *pContext, LPCSTR procname, LPCSTR reason)
+{
+    if (pContext->Offload.flagsValue & osbT6Lso)
+    {
+        DPrintf(0, ("[%s] Warning: %s", procname, reason));
+        pContext->Offload.flagsValue &= ~osbT6Lso;
+        ParaNdis_ResetOffloadSettings(pContext, NULL, NULL);
+    }
+}
+
+static void DisableBothLSOPermanently(PARANDIS_ADAPTER *pContext, LPCSTR procname, LPCSTR reason)
+{
+    if (pContext->Offload.flagsValue & (osbT4Lso | osbT6Lso))
+    {
+        DPrintf(0, ("[%s] Warning: %s", procname, reason));
+        pContext->Offload.flagsValue &= ~(osbT6Lso | osbT4Lso);
+        ParaNdis_ResetOffloadSettings(pContext, NULL, NULL);
+    }
+}
+
+/**********************************************************
+Loads NIC parameters from adapter registry key
+Parameters:
+    context
+    PUCHAR *ppNewMACAddress - pointer to hold MAC address if configured from host
+***********************************************************/
+static void ReadNicConfiguration(PARANDIS_ADAPTER *pContext, PUCHAR *ppNewMACAddress)
+{
+    NDIS_HANDLE cfg;
+    tConfigurationEntries *pConfiguration = ParaNdis_AllocateMemory(pContext, sizeof(tConfigurationEntries));
+    if (pConfiguration)
+    {
+        *pConfiguration = defaultConfiguration;
+        cfg = ParaNdis_OpenNICConfiguration(pContext);
+        if (cfg)
+        {
+            GetConfigurationEntry(cfg, &pConfiguration->isLogEnabled);
+            GetConfigurationEntry(cfg, &pConfiguration->debugLevel);
+            GetConfigurationEntry(cfg, &pConfiguration->ConnectRate);
+            GetConfigurationEntry(cfg, &pConfiguration->PrioritySupport);
+            GetConfigurationEntry(cfg, &pConfiguration->isPromiscuous);
+            GetConfigurationEntry(cfg, &pConfiguration->TxCapacity);
+            GetConfigurationEntry(cfg, &pConfiguration->RxCapacity);
+            GetConfigurationEntry(cfg, &pConfiguration->connectTimer);
+            GetConfigurationEntry(cfg, &pConfiguration->dpcChecker);
+            GetConfigurationEntry(cfg, &pConfiguration->InterruptRecovery);
+            GetConfigurationEntry(cfg, &pConfiguration->LogStatistics);
+            GetConfigurationEntry(cfg, &pConfiguration->PacketFiltering);
+            GetConfigurationEntry(cfg, &pConfiguration->ScatterGather);
+            GetConfigurationEntry(cfg, &pConfiguration->BatchReceive);
+            GetConfigurationEntry(cfg, &pConfiguration->OffloadTxChecksum);
+            GetConfigurationEntry(cfg, &pConfiguration->OffloadTxLSO);
+            GetConfigurationEntry(cfg, &pConfiguration->OffloadRxCS);
+            GetConfigurationEntry(cfg, &pConfiguration->OffloadGuestCS);
+            GetConfigurationEntry(cfg, &pConfiguration->UseSwTxChecksum);
+            GetConfigurationEntry(cfg, &pConfiguration->IPPacketsCheck);
+            GetConfigurationEntry(cfg, &pConfiguration->stdIpcsV4);
+            GetConfigurationEntry(cfg, &pConfiguration->stdTcpcsV4);
+            GetConfigurationEntry(cfg, &pConfiguration->stdTcpcsV6);
+            GetConfigurationEntry(cfg, &pConfiguration->stdUdpcsV4);
+            GetConfigurationEntry(cfg, &pConfiguration->stdUdpcsV6);
+            GetConfigurationEntry(cfg, &pConfiguration->stdLsoV1);
+            GetConfigurationEntry(cfg, &pConfiguration->stdLsoV2ip4);
+            GetConfigurationEntry(cfg, &pConfiguration->stdLsoV2ip6);
+            GetConfigurationEntry(cfg, &pConfiguration->PriorityVlanTagging);
+            GetConfigurationEntry(cfg, &pConfiguration->VlanId);
+            GetConfigurationEntry(cfg, &pConfiguration->UseMergeableBuffers);
+            GetConfigurationEntry(cfg, &pConfiguration->MTU);
+            GetConfigurationEntry(cfg, &pConfiguration->NumberOfHandledRXPackersInDPC);
+            GetConfigurationEntry(cfg, &pConfiguration->Indirect);
+
+    #if !defined(WPP_EVENT_TRACING)
+            bDebugPrint = pConfiguration->isLogEnabled.ulValue;
+            nDebugLevel = pConfiguration->debugLevel.ulValue;
+    #endif
+            // ignoring promiscuous setting, nothing to do with it
+            pContext->maxFreeTxDescriptors = pConfiguration->TxCapacity.ulValue;
+            pContext->NetMaxReceiveBuffers = pConfiguration->RxCapacity.ulValue;
+            pContext->ulMilliesToConnect = pConfiguration->connectTimer.ulValue;
+            pContext->nEnableDPCChecker = pConfiguration->dpcChecker.ulValue;
+            pContext->bDoInterruptRecovery = pConfiguration->InterruptRecovery.ulValue != 0;
+            pContext->Limits.nPrintDiagnostic = pConfiguration->LogStatistics.ulValue;
+            pContext->uNumberOfHandledRXPacketsInDPC = pConfiguration->NumberOfHandledRXPackersInDPC.ulValue;
+            pContext->bDoSupportPriority = pConfiguration->PrioritySupport.ulValue != 0;
+            pContext->ulFormalLinkSpeed  = pConfiguration->ConnectRate.ulValue;
+            pContext->ulFormalLinkSpeed *= 1000000;
+            pContext->bDoHwPacketFiltering = pConfiguration->PacketFiltering.ulValue != 0;
+            pContext->bUseScatterGather  = pConfiguration->ScatterGather.ulValue != 0;
+            pContext->bBatchReceive      = pConfiguration->BatchReceive.ulValue != 0;
+            pContext->bDoHardwareChecksum = pConfiguration->UseSwTxChecksum.ulValue == 0;
+            pContext->bDoGuestChecksumOnReceive = pConfiguration->OffloadGuestCS.ulValue != 0;
+            pContext->bDoIPCheckTx = pConfiguration->IPPacketsCheck.ulValue & 1;
+            pContext->bDoIPCheckRx = pConfiguration->IPPacketsCheck.ulValue & 2;
+            pContext->Offload.flagsValue = 0;
+            // TX caps: 1 - TCP, 2 - UDP, 4 - IP, 8 - TCPv6, 16 - UDPv6
+            if (pConfiguration->OffloadTxChecksum.ulValue & 1) pContext->Offload.flagsValue |= osbT4TcpChecksum | osbT4TcpOptionsChecksum;
+            if (pConfiguration->OffloadTxChecksum.ulValue & 2) pContext->Offload.flagsValue |= osbT4UdpChecksum;
+            if (pConfiguration->OffloadTxChecksum.ulValue & 4) pContext->Offload.flagsValue |= osbT4IpChecksum | osbT4IpOptionsChecksum;
+            if (pConfiguration->OffloadTxChecksum.ulValue & 8) pContext->Offload.flagsValue |= osbT6TcpChecksum | osbT6TcpOptionsChecksum;
+            if (pConfiguration->OffloadTxChecksum.ulValue & 16) pContext->Offload.flagsValue |= osbT6UdpChecksum;
+            if (pConfiguration->OffloadTxLSO.ulValue) pContext->Offload.flagsValue |= osbT4Lso | osbT4LsoIp | osbT4LsoTcp;
+            if (pConfiguration->OffloadTxLSO.ulValue > 1) pContext->Offload.flagsValue |= osbT6Lso | osbT6LsoTcpOptions;
+            // RX caps: 1 - TCP, 2 - UDP, 4 - IP, 8 - TCPv6, 16 - UDPv6
+            if (pConfiguration->OffloadRxCS.ulValue & 1) pContext->Offload.flagsValue |= osbT4RxTCPChecksum | osbT4RxTCPOptionsChecksum;
+            if (pConfiguration->OffloadRxCS.ulValue & 2) pContext->Offload.flagsValue |= osbT4RxUDPChecksum;
+            if (pConfiguration->OffloadRxCS.ulValue & 4) pContext->Offload.flagsValue |= osbT4RxIPChecksum | osbT4RxIPOptionsChecksum;
+            if (pConfiguration->OffloadRxCS.ulValue & 8) pContext->Offload.flagsValue |= osbT6RxTCPChecksum | osbT6RxTCPOptionsChecksum;
+            if (pConfiguration->OffloadRxCS.ulValue & 16) pContext->Offload.flagsValue |= osbT6RxUDPChecksum;
+            /* full packet size that can be configured as GSO for VIRTIO is short */
+            /* NDIS test fails sometimes fails on segments 50-60K */
+            pContext->Offload.maxPacketSize = PARANDIS_MAX_LSO_SIZE;
+            pContext->InitialOffloadParameters.IPv4Checksum = (UCHAR)pConfiguration->stdIpcsV4.ulValue;
+            pContext->InitialOffloadParameters.TCPIPv4Checksum = (UCHAR)pConfiguration->stdTcpcsV4.ulValue;
+            pContext->InitialOffloadParameters.TCPIPv6Checksum = (UCHAR)pConfiguration->stdTcpcsV6.ulValue;
+            pContext->InitialOffloadParameters.UDPIPv4Checksum = (UCHAR)pConfiguration->stdUdpcsV4.ulValue;
+            pContext->InitialOffloadParameters.UDPIPv6Checksum = (UCHAR)pConfiguration->stdUdpcsV6.ulValue;
+            pContext->InitialOffloadParameters.LsoV1 = (UCHAR)pConfiguration->stdLsoV1.ulValue;
+            pContext->InitialOffloadParameters.LsoV2IPv4 = (UCHAR)pConfiguration->stdLsoV2ip4.ulValue;
+            pContext->InitialOffloadParameters.LsoV2IPv6 = (UCHAR)pConfiguration->stdLsoV2ip6.ulValue;
+            pContext->ulPriorityVlanSetting = pConfiguration->PriorityVlanTagging.ulValue;
+            pContext->VlanId = pConfiguration->VlanId.ulValue & 0xfff;
+            pContext->bUseMergedBuffers = pConfiguration->UseMergeableBuffers.ulValue != 0;
+            pContext->MaxPacketSize.nMaxDataSize = pConfiguration->MTU.ulValue;
+            pContext->bUseIndirect = pConfiguration->Indirect.ulValue != 0;
+            if (!pContext->bDoSupportPriority)
+                pContext->ulPriorityVlanSetting = 0;
+            // if Vlan not supported
+            if (!IsVlanSupported(pContext))
+                pContext->VlanId = 0;
+            if (1)
+            {
+                NDIS_STATUS status;
+                PVOID p;
+                UINT  len = 0;
+                NdisReadNetworkAddress(&status, &p, &len, cfg);
+                if (status == NDIS_STATUS_SUCCESS && len == sizeof(pContext->CurrentMacAddress))
+                {
+                    *ppNewMACAddress = ParaNdis_AllocateMemory(pContext, sizeof(pContext->CurrentMacAddress));
+                    if (*ppNewMACAddress)
+                    {
+                        NdisMoveMemory(*ppNewMACAddress, p, len);
+                    }
+                    else
+                    {
+                        DPrintf(0, ("[%s] MAC address present, but some problem also...", __FUNCTION__));
+                    }
+                }
+                else if (len && len != sizeof(pContext->CurrentMacAddress))
+                {
+                    DPrintf(0, ("[%s] MAC address has wrong length of %d", __FUNCTION__, len));
+                }
+                else
+                {
+                    DPrintf(4, ("[%s] Nothing read for MAC, error %X", __FUNCTION__, status));
+                }
+            }
+            NdisCloseConfiguration(cfg);
+        }
+        NdisFreeMemory(pConfiguration, 0, 0);
+    }
+}
+
+void ParaNdis_ResetOffloadSettings(PARANDIS_ADAPTER *pContext, tOffloadSettingsFlags *pDest, PULONG from)
+{
+    if (!pDest) pDest = &pContext->Offload.flags;
+    if (!from)  from = &pContext->Offload.flagsValue;
+
+    pDest->fTxIPChecksum = !!(*from & osbT4IpChecksum);
+    pDest->fTxTCPChecksum = !!(*from & osbT4TcpChecksum);
+    pDest->fTxUDPChecksum = !!(*from & osbT4UdpChecksum);
+    pDest->fTxTCPOptions = !!(*from & osbT4TcpOptionsChecksum);
+    pDest->fTxIPOptions = !!(*from & osbT4IpOptionsChecksum);
+
+    pDest->fTxLso = !!(*from & osbT4Lso);
+    pDest->fTxLsoIP = !!(*from & osbT4LsoIp);
+    pDest->fTxLsoTCP = !!(*from & osbT4LsoTcp);
+
+    pDest->fRxIPChecksum = !!(*from & osbT4RxIPChecksum);
+    pDest->fRxIPOptions = !!(*from & osbT4RxIPOptionsChecksum);
+    pDest->fRxTCPChecksum = !!(*from & osbT4RxTCPChecksum);
+    pDest->fRxTCPOptions = !!(*from & osbT4RxTCPOptionsChecksum);
+    pDest->fRxUDPChecksum = !!(*from & osbT4RxUDPChecksum);
+
+    pDest->fTxTCPv6Checksum = !!(*from & osbT6TcpChecksum);
+    pDest->fTxTCPv6Options = !!(*from & osbT6TcpOptionsChecksum);
+    pDest->fTxUDPv6Checksum = !!(*from & osbT6UdpChecksum);
+    pDest->fTxIPv6Ext = !!(*from & osbT6IpExtChecksum);
+
+    pDest->fTxLsov6 = !!(*from & osbT6Lso);
+    pDest->fTxLsov6IP = !!(*from & osbT6LsoIpExt);
+    pDest->fTxLsov6TCP = !!(*from & osbT6LsoTcpOptions);
+
+    pDest->fRxTCPv6Checksum = !!(*from & osbT6RxTCPChecksum);
+    pDest->fRxTCPv6Options = !!(*from & osbT6RxTCPOptionsChecksum);
+    pDest->fRxUDPv6Checksum = !!(*from & osbT6RxUDPChecksum);
+    pDest->fRxIPv6Ext = !!(*from & osbT6RxIpExtChecksum);
+}
+
+/**********************************************************
+Enumerates adapter resources and fills the structure holding them
+Verifies that IO assigned and has correct size
+Verifies that interrupt assigned
+Parameters:
+    PNDIS_RESOURCE_LIST RList - list of resources, received from NDIS
+    tAdapterResources *pResources - structure to fill
+Return value:
+    TRUE if everything is OK
+***********************************************************/
+static BOOLEAN GetAdapterResources(NDIS_HANDLE MiniportHandle, PNDIS_RESOURCE_LIST RList, tAdapterResources *pResources)
+{
+    UINT i;
+    int read, bar = -1;
+    PCI_COMMON_HEADER pci_config;
+    NdisZeroMemory(pResources, sizeof(*pResources));
+
+    // read the PCI config space header
+    read = NdisReadPciSlotInformation(
+       MiniportHandle,
+       0 /* SlotNumber, reserved */,
+       0 /* Offset */,
+       &pci_config,
+       sizeof(pci_config));
+    if (read != sizeof(pci_config)) {
+       return FALSE;
+    }
+
+    for (i = 0; i < RList->Count; ++i)
+    {
+        ULONG type = RList->PartialDescriptors[i].Type;
+        if (type == CmResourceTypePort)
+        {
+            PHYSICAL_ADDRESS Start = RList->PartialDescriptors[i].u.Port.Start;
+            ULONG len = RList->PartialDescriptors[i].u.Port.Length;
+            DPrintf(0, ("Found IO ports at %08lX(%d)", Start.LowPart, len));
+            bar = virtio_get_bar_index(&pci_config, Start);
+            if (bar < 0) {
+               break;
+            }
+            pResources->PciBars[bar].BasePA = Start;
+            pResources->PciBars[bar].uLength = len;
+            pResources->PciBars[bar].bPortSpace = TRUE;
+        }
+        else if (type == CmResourceTypeMemory)
+        {
+            PHYSICAL_ADDRESS Start = RList->PartialDescriptors[i].u.Memory.Start;
+            ULONG len = RList->PartialDescriptors[i].u.Memory.Length;
+            DPrintf(0, ("Found IO memory at %08I64X(%d)", Start.QuadPart, len));
+            bar = virtio_get_bar_index(&pci_config, Start);
+            if (bar < 0) {
+               break;
+            }
+            pResources->PciBars[bar].BasePA = Start;
+            pResources->PciBars[bar].uLength = len;
+            pResources->PciBars[bar].bPortSpace = FALSE;
+        }
+        else if (type == CmResourceTypeInterrupt)
+        {
+            pResources->Vector = RList->PartialDescriptors[i].u.Interrupt.Vector;
+            pResources->Level = RList->PartialDescriptors[i].u.Interrupt.Level;
+            pResources->Affinity = RList->PartialDescriptors[i].u.Interrupt.Affinity;
+            pResources->InterruptFlags = RList->PartialDescriptors[i].Flags;
+            DPrintf(0, ("Found Interrupt vector %d, level %d, affinity %X, flags %X",
+                pResources->Vector, pResources->Level, (ULONG)pResources->Affinity, pResources->InterruptFlags));
+        }
+    }
+    return bar >= 0 && pResources->Vector;
+}
+
+static void DumpVirtIOFeatures(PARANDIS_ADAPTER *pContext)
+{
+    const struct {  ULONG bitmask;  const PCHAR Name; } Features[] =
+    {
+
+        {VIRTIO_NET_F_CSUM, "VIRTIO_NET_F_CSUM" },
+        {VIRTIO_NET_F_GUEST_CSUM, "VIRTIO_NET_F_GUEST_CSUM" },
+        {VIRTIO_NET_F_MAC, "VIRTIO_NET_F_MAC" },
+        {VIRTIO_NET_F_GSO, "VIRTIO_NET_F_GSO" },
+        {VIRTIO_NET_F_GUEST_TSO4, "VIRTIO_NET_F_GUEST_TSO4"},
+        {VIRTIO_NET_F_GUEST_TSO6, "VIRTIO_NET_F_GUEST_TSO6"},
+        {VIRTIO_NET_F_GUEST_ECN, "VIRTIO_NET_F_GUEST_ECN"},
+        {VIRTIO_NET_F_GUEST_UFO, "VIRTIO_NET_F_GUEST_UFO"},
+        {VIRTIO_NET_F_HOST_TSO4, "VIRTIO_NET_F_HOST_TSO4"},
+        {VIRTIO_NET_F_HOST_TSO6, "VIRTIO_NET_F_HOST_TSO6"},
+        {VIRTIO_NET_F_HOST_ECN, "VIRTIO_NET_F_HOST_ECN"},
+        {VIRTIO_NET_F_HOST_UFO, "VIRTIO_NET_F_HOST_UFO"},
+        {VIRTIO_NET_F_MRG_RXBUF, "VIRTIO_NET_F_MRG_RXBUF"},
+        {VIRTIO_NET_F_STATUS, "VIRTIO_NET_F_STATUS"},
+        {VIRTIO_NET_F_CTRL_VQ, "VIRTIO_NET_F_CTRL_VQ"},
+        {VIRTIO_NET_F_CTRL_RX, "VIRTIO_NET_F_CTRL_RX"},
+        {VIRTIO_NET_F_CTRL_VLAN, "VIRTIO_NET_F_CTRL_VLAN"},
+        {VIRTIO_NET_F_CTRL_RX_EXTRA, "VIRTIO_NET_F_CTRL_RX_EXTRA"},
+        {VIRTIO_RING_F_INDIRECT_DESC, "VIRTIO_RING_F_INDIRECT_DESC"},
+        {VIRTIO_F_VERSION_1, "VIRTIO_F_VERSION_1" },
+        {VIRTIO_F_ANY_LAYOUT, "VIRTIO_F_ANY_LAYOUT" },
+    };
+    UINT i;
+    for (i = 0; i < sizeof(Features)/sizeof(Features[0]); ++i)
+    {
+        if (VirtIODeviceGetHostFeature(pContext, Features[i].bitmask))
+        {
+            DPrintf(0, ("VirtIO Host Feature %s", Features[i].Name));
+        }
+    }
+}
+
+/**********************************************************
+    Only for test. Prints out if the interrupt line is ON
+Parameters:
+Return value:
+***********************************************************/
+static void JustForCheckClearInterrupt(PARANDIS_ADAPTER *pContext, const char *Label)
+{
+    if (pContext->bEnableInterruptChecking)
+    {
+        ULONG ulActive;
+        ulActive = virtio_read_isr_status(&pContext->IODevice);
+        if (ulActive)
+        {
+            DPrintf(0,("WARNING: Interrupt Line %d(%s)!", ulActive, Label));
+        }
+    }
+}
+
+/**********************************************************
+Prints out statistics
+***********************************************************/
+static void PrintStatistics(PARANDIS_ADAPTER *pContext)
+{
+    ULONG64 totalTxFrames =
+        pContext->Statistics.ifHCOutBroadcastPkts +
+        pContext->Statistics.ifHCOutMulticastPkts +
+        pContext->Statistics.ifHCOutUcastPkts;
+    ULONG64 totalRxFrames =
+        pContext->Statistics.ifHCInBroadcastPkts +
+        pContext->Statistics.ifHCInMulticastPkts +
+        pContext->Statistics.ifHCInUcastPkts;
+
+    DPrintf(0, ("[Diag!%X] RX buffers at VIRTIO %d of %d",
+        pContext->CurrentMacAddress[5],
+        pContext->NetNofReceiveBuffers,
+        pContext->NetMaxReceiveBuffers));
+    DPrintf(0, ("[Diag!] TX desc available %d/%d, buf %d/min. %d",
+        pContext->nofFreeTxDescriptors,
+        pContext->maxFreeTxDescriptors,
+        pContext->nofFreeHardwareBuffers,
+        pContext->minFreeHardwareBuffers));
+    pContext->minFreeHardwareBuffers = pContext->nofFreeHardwareBuffers;
+    if (pContext->NetTxPacketsToReturn)
+    {
+        DPrintf(0, ("[Diag!] TX packets to return %d", pContext->NetTxPacketsToReturn));
+    }
+    DPrintf(0, ("[Diag!] Bytes transmitted %I64u, received %I64u",
+        pContext->Statistics.ifHCOutOctets,
+        pContext->Statistics.ifHCInOctets));
+    DPrintf(0, ("[Diag!] Tx frames %I64u, CSO %d, LSO %d, indirect %d",
+        totalTxFrames,
+        pContext->extraStatistics.framesCSOffload,
+        pContext->extraStatistics.framesLSO,
+        pContext->extraStatistics.framesIndirect));
+    DPrintf(0, ("[Diag!] Rx frames %I64u, Rx.Pri %d, RxHwCS.OK %d, FiltOut %d",
+        totalRxFrames, pContext->extraStatistics.framesRxPriority,
+        pContext->extraStatistics.framesRxCSHwOK, pContext->extraStatistics.framesFilteredOut));
+    if (pContext->extraStatistics.framesRxCSHwMissedBad || pContext->extraStatistics.framesRxCSHwMissedGood)
+    {
+        DPrintf(0, ("[Diag!] RxHwCS mistakes: missed bad %d, missed good %d",
+            pContext->extraStatistics.framesRxCSHwMissedBad, pContext->extraStatistics.framesRxCSHwMissedGood));
+    }
+}
+
+static NDIS_STATUS NTStatusToNdisStatus(NTSTATUS nt_status) {
+    switch (nt_status) {
+    case STATUS_SUCCESS:
+        return NDIS_STATUS_SUCCESS;
+    case STATUS_NOT_FOUND:
+    case STATUS_DEVICE_NOT_CONNECTED:
+        return NDIS_STATUS_ADAPTER_NOT_FOUND;
+    case STATUS_INSUFFICIENT_RESOURCES:
+        return NDIS_STATUS_RESOURCES;
+    case STATUS_INVALID_PARAMETER:
+        return NDIS_STATUS_INVALID_DEVICE_REQUEST;
+    default:
+        return NDIS_STATUS_FAILURE;
+    }
+}
+
+static NDIS_STATUS FinalizeFeatures(PARANDIS_ADAPTER *pContext)
+{
+    NTSTATUS nt_status = virtio_set_features(&pContext->IODevice, pContext->ullGuestFeatures);
+    if (!NT_SUCCESS(nt_status)) {
+        DPrintf(0, ("[%s] virtio_set_features failed with %x\n", __FUNCTION__, nt_status));
+    }
+    return NTStatusToNdisStatus(nt_status);
+}
+
+/**********************************************************
+Initializes the context structure
+Major variables, received from NDIS on initialization, must be be set before this call
+(for ex. pContext->MiniportHandle)
+
+If this procedure fails, no need to call
+    ParaNdis_CleanupContext
+
+
+Parameters:
+Return value:
+    SUCCESS, if resources are OK
+    NDIS_STATUS_RESOURCE_CONFLICT if not
+***********************************************************/
+NDIS_STATUS ParaNdis_InitializeContext(
+    PARANDIS_ADAPTER *pContext,
+    PNDIS_RESOURCE_LIST pResourceList)
+{
+    NDIS_STATUS status = NDIS_STATUS_SUCCESS;
+    PUCHAR pNewMacAddress = NULL;
+    USHORT linkStatus = 0;
+    NTSTATUS nt_status;
+
+    DEBUG_ENTRY(0);
+    /* read first PCI IO bar*/
+    //ulIOAddress = ReadPCIConfiguration(miniportAdapterHandle, 0x10);
+    /* check this is IO and assigned */
+    ReadNicConfiguration(pContext, &pNewMacAddress);
+    if (pNewMacAddress)
+    {
+        if (ParaNdis_ValidateMacAddress(pNewMacAddress, TRUE))
+        {
+            DPrintf(0, ("[%s] WARNING: MAC address reloaded", __FUNCTION__));
+            NdisMoveMemory(pContext->CurrentMacAddress, pNewMacAddress, sizeof(pContext->CurrentMacAddress));
+        }
+        else
+        {
+            DPrintf(0, ("[%s] WARNING: Invalid MAC address ignored", __FUNCTION__));
+        }
+        NdisFreeMemory(pNewMacAddress, 0, 0);
+    }
+
+    pContext->MaxPacketSize.nMaxFullSizeOS = pContext->MaxPacketSize.nMaxDataSize + ETH_HEADER_SIZE;
+    pContext->MaxPacketSize.nMaxFullSizeHwTx = pContext->MaxPacketSize.nMaxFullSizeOS;
+    pContext->MaxPacketSize.nMaxFullSizeHwRx = pContext->MaxPacketSize.nMaxFullSizeOS + ETH_PRIORITY_HEADER_SIZE;
+    if (pContext->ulPriorityVlanSetting)
+        pContext->MaxPacketSize.nMaxFullSizeHwTx = pContext->MaxPacketSize.nMaxFullSizeHwRx;
+
+    if (GetAdapterResources(pContext->MiniportHandle, pResourceList, &pContext->AdapterResources))
+    {
+        if (pContext->AdapterResources.InterruptFlags & CM_RESOURCE_INTERRUPT_MESSAGE)
+        {
+            DPrintf(0, ("[%s] Message interrupt assigned", __FUNCTION__));
+            pContext->bUsingMSIX = TRUE;
+        }
+
+        nt_status = virtio_device_initialize(
+            &pContext->IODevice,
+            &ParaNdisSystemOps,
+            pContext,
+            pContext->bUsingMSIX);
+        if (!NT_SUCCESS(nt_status)) {
+            DPrintf(0, ("[%s] virtio_device_initialize failed with %x\n", __FUNCTION__, nt_status));
+            status = NTStatusToNdisStatus(nt_status);
+            DEBUG_EXIT_STATUS(0, status);
+            return status;
+        }
+
+        pContext->bIODeviceInitialized = TRUE;
+        JustForCheckClearInterrupt(pContext, "init 0");
+        ParaNdis_ResetVirtIONetDevice(pContext);
+        JustForCheckClearInterrupt(pContext, "init 1");
+        virtio_add_status(&pContext->IODevice, VIRTIO_CONFIG_S_ACKNOWLEDGE);
+        JustForCheckClearInterrupt(pContext, "init 2");
+        virtio_add_status(&pContext->IODevice, VIRTIO_CONFIG_S_DRIVER);
+        pContext->ullHostFeatures = virtio_get_features(&pContext->IODevice);
+        DumpVirtIOFeatures(pContext);
+        JustForCheckClearInterrupt(pContext, "init 3");
+        pContext->bLinkDetectSupported = 0 != VirtIODeviceGetHostFeature(pContext, VIRTIO_NET_F_STATUS);
+
+        if(pContext->bLinkDetectSupported) {
+            virtio_get_config(&pContext->IODevice, sizeof(pContext->CurrentMacAddress), &linkStatus, sizeof(linkStatus));
+            pContext->bConnected = (linkStatus & VIRTIO_NET_S_LINK_UP) != 0;
+            DPrintf(0, ("[%s] Link status on driver startup: %d", __FUNCTION__, pContext->bConnected));
+        }
+
+        if (VirtIODeviceGetHostFeature(pContext, VIRTIO_F_VERSION_1))
+        {
+            // virtio 1.0 always uses the extended header
+            pContext->nVirtioHeaderSize = sizeof(virtio_net_hdr_ext);
+            VirtIODeviceEnableGuestFeature(pContext, VIRTIO_F_VERSION_1);
+        }
+        else
+        {
+            pContext->nVirtioHeaderSize = sizeof(virtio_net_hdr_basic);
+        }
+
+        if (VirtIODeviceGetHostFeature(pContext, VIRTIO_F_ANY_LAYOUT))
+        {
+            VirtIODeviceEnableGuestFeature(pContext, VIRTIO_F_ANY_LAYOUT);
+        }
+        if (VirtIODeviceGetHostFeature(pContext, VIRTIO_RING_F_EVENT_IDX))
+        {
+            VirtIODeviceEnableGuestFeature(pContext, VIRTIO_RING_F_EVENT_IDX);
+        }
+
+        if (!pContext->bUseMergedBuffers && VirtIODeviceGetHostFeature(pContext, VIRTIO_NET_F_MRG_RXBUF))
+        {
+            DPrintf(0, ("[%s] Not using mergeable buffers", __FUNCTION__));
+        }
+        else
+        {
+            pContext->bUseMergedBuffers = VirtIODeviceGetHostFeature(pContext, VIRTIO_NET_F_MRG_RXBUF) != 0;
+            if (pContext->bUseMergedBuffers)
+            {
+                pContext->nVirtioHeaderSize = sizeof(virtio_net_hdr_ext);
+                VirtIODeviceEnableGuestFeature(pContext, VIRTIO_NET_F_MRG_RXBUF);
+            }
+        }
+        if (VirtIODeviceGetHostFeature(pContext, VIRTIO_NET_F_MAC))
+        {
+            virtio_get_config(
+                &pContext->IODevice,
+                0, // + offsetof(struct virtio_net_config, mac)
+                &pContext->PermanentMacAddress,
+                ETH_LENGTH_OF_ADDRESS);
+            if (!ParaNdis_ValidateMacAddress(pContext->PermanentMacAddress, FALSE))
+            {
+                DPrintf(0,("Invalid device MAC ignored(%02x-%02x-%02x-%02x-%02x-%02x)",
+                    pContext->PermanentMacAddress[0],
+                    pContext->PermanentMacAddress[1],
+                    pContext->PermanentMacAddress[2],
+                    pContext->PermanentMacAddress[3],
+                    pContext->PermanentMacAddress[4],
+                    pContext->PermanentMacAddress[5]));
+                NdisZeroMemory(pContext->PermanentMacAddress, sizeof(pContext->PermanentMacAddress));
+            }
+        }
+
+        if (ETH_IS_EMPTY(pContext->PermanentMacAddress))
+        {
+            DPrintf(0, ("No device MAC present, use default"));
+            pContext->PermanentMacAddress[0] = 0x02;
+            pContext->PermanentMacAddress[1] = 0x50;
+            pContext->PermanentMacAddress[2] = 0xF2;
+            pContext->PermanentMacAddress[3] = 0x00;
+            pContext->PermanentMacAddress[4] = 0x01;
+            pContext->PermanentMacAddress[5] = 0x80 | (UCHAR)(pContext->ulUniqueID & 0xFF);
+        }
+        DPrintf(0,("Device MAC = %02x-%02x-%02x-%02x-%02x-%02x",
+            pContext->PermanentMacAddress[0],
+            pContext->PermanentMacAddress[1],
+            pContext->PermanentMacAddress[2],
+            pContext->PermanentMacAddress[3],
+            pContext->PermanentMacAddress[4],
+            pContext->PermanentMacAddress[5]));
+
+        if (ETH_IS_EMPTY(pContext->CurrentMacAddress))
+        {
+            NdisMoveMemory(
+                &pContext->CurrentMacAddress,
+                &pContext->PermanentMacAddress,
+                ETH_LENGTH_OF_ADDRESS);
+        }
+        else
+        {
+            DPrintf(0,("Current MAC = %02x-%02x-%02x-%02x-%02x-%02x",
+                pContext->CurrentMacAddress[0],
+                pContext->CurrentMacAddress[1],
+                pContext->CurrentMacAddress[2],
+                pContext->CurrentMacAddress[3],
+                pContext->CurrentMacAddress[4],
+                pContext->CurrentMacAddress[5]));
+        }
+        if (VirtIODeviceGetHostFeature(pContext, VIRTIO_NET_F_CTRL_VQ)) {
+            pContext->bHasControlQueue = TRUE;
+            VirtIODeviceEnableGuestFeature(pContext, VIRTIO_NET_F_CTRL_VQ);
+        }
+    }
+    else
+    {
+        DPrintf(0, ("[%s] Error: Incomplete resources", __FUNCTION__));
+        status = NDIS_STATUS_RESOURCE_CONFLICT;
+    }
+
+
+    if (pContext->bDoHardwareChecksum)
+    {
+        ULONG dependentOptions;
+        dependentOptions = osbT4TcpChecksum | osbT4UdpChecksum | osbT4TcpOptionsChecksum;
+        if (!VirtIODeviceGetHostFeature(pContext, VIRTIO_NET_F_CSUM) &&
+            (pContext->Offload.flagsValue & dependentOptions))
+        {
+            DPrintf(0, ("[%s] Host does not support CSUM, disabling CS offload", __FUNCTION__) );
+            pContext->Offload.flagsValue &= ~dependentOptions;
+        }
+    }
+
+    if (pContext->bDoGuestChecksumOnReceive && VirtIODeviceGetHostFeature(pContext, VIRTIO_NET_F_GUEST_CSUM))
+    {
+        DPrintf(0, ("[%s] Enabling guest checksum", __FUNCTION__) );
+        VirtIODeviceEnableGuestFeature(pContext, VIRTIO_NET_F_GUEST_CSUM);
+    }
+    else
+    {
+        pContext->bDoGuestChecksumOnReceive = FALSE;
+    }
+
+    // now, after we checked the capabilities, we can initialize current
+    // configuration of offload tasks
+    ParaNdis_ResetOffloadSettings(pContext, NULL, NULL);
+    if (pContext->Offload.flags.fTxLso && !pContext->bUseScatterGather)
+    {
+        DisableBothLSOPermanently(pContext, __FUNCTION__, "SG is not active");
+    }
+    if (pContext->Offload.flags.fTxLso &&
+        !VirtIODeviceGetHostFeature(pContext, VIRTIO_NET_F_HOST_TSO4))
+    {
+        DisableLSOv4Permanently(pContext, __FUNCTION__, "Host does not support TSOv4");
+    }
+    if (pContext->Offload.flags.fTxLsov6 &&
+        !VirtIODeviceGetHostFeature(pContext, VIRTIO_NET_F_HOST_TSO6))
+    {
+        DisableLSOv6Permanently(pContext, __FUNCTION__, "Host does not support TSOv6");
+    }
+    if (pContext->bUseIndirect)
+    {
+        const char *reason = "";
+        if (!VirtIODeviceGetHostFeature(pContext, VIRTIO_RING_F_INDIRECT_DESC))
+        {
+            pContext->bUseIndirect = FALSE;
+            reason = "Host support";
+        }
+        else if (!pContext->bUseScatterGather)
+        {
+            pContext->bUseIndirect = FALSE;
+            reason = "SG";
+        }
+        DPrintf(0, ("[%s] %sable indirect Tx(!%s)", __FUNCTION__, pContext->bUseIndirect ? "En" : "Dis", reason) );
+    }
+
+    if (VirtIODeviceGetHostFeature(pContext, VIRTIO_NET_F_CTRL_RX_EXTRA) &&
+        pContext->bDoHwPacketFiltering)
+    {
+        DPrintf(0, ("[%s] Using hardware packet filtering", __FUNCTION__));
+        pContext->bHasHardwareFilters = TRUE;
+    }
+
+    status = FinalizeFeatures(pContext);
+
+    pContext->ReuseBufferProc = (tReuseReceiveBufferProc)ReuseReceiveBufferRegular;
+    
+    NdisInitializeEvent(&pContext->ResetEvent);
+    DEBUG_EXIT_STATUS(0, status);
+    return status;
+}
+
+/**********************************************************
+Free the resources allocated for VirtIO buffer descriptor
+Parameters:
+    PVOID pParam                pIONetDescriptor to free
+    BOOLEAN bRemoveFromList     TRUE, if also remove it from list
+***********************************************************/
+static void VirtIONetFreeBufferDescriptor(PARANDIS_ADAPTER *pContext, pIONetDescriptor pBufferDescriptor)
+{
+    if(pBufferDescriptor)
+    {
+        if (pBufferDescriptor->pHolder)
+            ParaNdis_UnbindBufferFromPacket(pContext, pBufferDescriptor);
+        if (pBufferDescriptor->DataInfo.Virtual)
+            ParaNdis_FreePhysicalMemory(pContext, &pBufferDescriptor->DataInfo);
+        if (pBufferDescriptor->HeaderInfo.Virtual)
+            ParaNdis_FreePhysicalMemory(pContext, &pBufferDescriptor->HeaderInfo);
+        NdisFreeMemory(pBufferDescriptor, 0, 0);
+    }
+}
+
+/**********************************************************
+Free all the buffer descriptors from specified list
+Parameters:
+    PLIST_ENTRY pListRoot           list containing pIONetDescriptor structures
+    PNDIS_SPIN_LOCK pLock           lock to protest this list
+Return value:
+***********************************************************/
+static void FreeDescriptorsFromList(PARANDIS_ADAPTER *pContext, PLIST_ENTRY pListRoot, PNDIS_SPIN_LOCK pLock)
+{
+    pIONetDescriptor pBufferDescriptor;
+    LIST_ENTRY TempList;
+    InitializeListHead(&TempList);
+    NdisAcquireSpinLock(pLock);
+    while(!IsListEmpty(pListRoot))
+    {
+        pBufferDescriptor = (pIONetDescriptor)RemoveHeadList(pListRoot);
+        InsertTailList(&TempList, &pBufferDescriptor->listEntry);
+    }
+    NdisReleaseSpinLock(pLock);
+    while(!IsListEmpty(&TempList))
+    {
+        pBufferDescriptor = (pIONetDescriptor)RemoveHeadList(&TempList);
+        VirtIONetFreeBufferDescriptor(pContext, pBufferDescriptor);
+    }
+}
+
+static pIONetDescriptor AllocatePairOfBuffersOnInit(
+    PARANDIS_ADAPTER *pContext,
+    ULONG size1,
+    ULONG size2,
+    BOOLEAN bForTx)
+{
+    pIONetDescriptor p;
+    p = (pIONetDescriptor)ParaNdis_AllocateMemory(pContext, sizeof(*p));
+    if (p)
+    {
+        BOOLEAN b1 = FALSE, b2 = FALSE;
+        NdisZeroMemory(p, sizeof(*p));
+        p->HeaderInfo.size = size1;
+        p->DataInfo.size   = size2;
+        p->HeaderInfo.IsCached = p->DataInfo.IsCached = 1;
+        p->HeaderInfo.IsTX = p->DataInfo.IsTX = bForTx;
+        p->nofUsedBuffers = 0;
+        b1 = ParaNdis_InitialAllocatePhysicalMemory(pContext, &p->HeaderInfo);
+        if (b1) b2 = ParaNdis_InitialAllocatePhysicalMemory(pContext, &p->DataInfo);
+        if (b1 && b2)
+        {
+            BOOLEAN b = bForTx || ParaNdis_BindBufferToPacket(pContext, p);
+            if (!b)
+            {
+                DPrintf(0, ("[INITPHYS](%s) Failed to bind memory to net packet", bForTx ? "TX" : "RX"));
+                VirtIONetFreeBufferDescriptor(pContext, p);
+                p = NULL;
+            }
+        }
+        else
+        {
+            if (b1) ParaNdis_FreePhysicalMemory(pContext, &p->HeaderInfo);
+            if (b2) ParaNdis_FreePhysicalMemory(pContext, &p->DataInfo);
+            NdisFreeMemory(p, 0, 0);
+            p = NULL;
+            DPrintf(0, ("[INITPHYS](%s) Failed to allocate memory block", bForTx ? "TX" : "RX"));
+        }
+    }
+    if (p)
+    {
+        DPrintf(3, ("[INITPHYS](%s) Header v%p(p%08lX), Data v%p(p%08lX)", bForTx ? "TX" : "RX",
+            p->HeaderInfo.Virtual, p->HeaderInfo.Physical.LowPart,
+            p->DataInfo.Virtual, p->DataInfo.Physical.LowPart));
+    }
+    return p;
+}
+
+/**********************************************************
+Allocates TX buffers according to startup setting (pContext->maxFreeTxDescriptors as got from registry)
+Buffers are chained in NetFreeSendBuffers
+Parameters:
+    context
+***********************************************************/
+static void PrepareTransmitBuffers(PARANDIS_ADAPTER *pContext)
+{
+    UINT nBuffers, nMaxBuffers;
+    DEBUG_ENTRY(4);
+    nMaxBuffers = virtio_get_queue_size(pContext->NetSendQueue) / 2;
+    if (nMaxBuffers > pContext->maxFreeTxDescriptors) nMaxBuffers = pContext->maxFreeTxDescriptors;
+
+    for (nBuffers = 0; nBuffers < nMaxBuffers; ++nBuffers)
+    {
+        pIONetDescriptor pBuffersDescriptor =
+            AllocatePairOfBuffersOnInit(
+                pContext,
+                pContext->nVirtioHeaderSize,
+                pContext->MaxPacketSize.nMaxFullSizeHwTx,
+                TRUE);
+        if (!pBuffersDescriptor) break;
+
+        NdisZeroMemory(pBuffersDescriptor->HeaderInfo.Virtual, pBuffersDescriptor->HeaderInfo.size);
+        InsertTailList(&pContext->NetFreeSendBuffers, &pBuffersDescriptor->listEntry);
+        pContext->nofFreeTxDescriptors++;
+    }
+
+    pContext->maxFreeTxDescriptors = pContext->nofFreeTxDescriptors;
+    pContext->nofFreeHardwareBuffers = pContext->nofFreeTxDescriptors * 2;
+    pContext->maxFreeHardwareBuffers = pContext->minFreeHardwareBuffers = pContext->nofFreeHardwareBuffers;
+    DPrintf(0, ("[%s] available %d Tx descriptors, %d hw buffers",
+        __FUNCTION__, pContext->nofFreeTxDescriptors, pContext->nofFreeHardwareBuffers));
+}
+
+static BOOLEAN AddRxBufferToQueue(PARANDIS_ADAPTER *pContext, pIONetDescriptor pBufferDescriptor)
+{
+    UINT nBuffersToSubmit = 2;
+    struct VirtIOBufferDescriptor sg[2];
+    if (!pContext->bUseMergedBuffers)
+    {
+        sg[0].physAddr = pBufferDescriptor->HeaderInfo.Physical;
+        sg[0].length = pBufferDescriptor->HeaderInfo.size;
+        sg[1].physAddr = pBufferDescriptor->DataInfo.Physical;
+        sg[1].length = pBufferDescriptor->DataInfo.size;
+    }
+    else
+    {
+        sg[0].physAddr = pBufferDescriptor->DataInfo.Physical;
+        sg[0].length = pBufferDescriptor->DataInfo.size;
+        nBuffersToSubmit = 1;
+    }
+    return 0 <= virtqueue_add_buf(
+        pContext->NetReceiveQueue,
+        sg,
+        0,
+        nBuffersToSubmit,
+        pBufferDescriptor,
+        NULL,
+        0);
+}
+
+
+/**********************************************************
+Allocates maximum RX buffers for incoming packets
+Buffers are chained in NetReceiveBuffers
+Parameters:
+    context
+***********************************************************/
+static int PrepareReceiveBuffers(PARANDIS_ADAPTER *pContext)
+{
+    int nRet = 0;
+    UINT i;
+    DEBUG_ENTRY(4);
+
+    for (i = 0; i < pContext->NetMaxReceiveBuffers; ++i)
+    {
+        ULONG size1 = pContext->bUseMergedBuffers ? 4 : pContext->nVirtioHeaderSize;
+        ULONG size2 = pContext->MaxPacketSize.nMaxFullSizeHwRx +
+            (pContext->bUseMergedBuffers ? pContext->nVirtioHeaderSize : 0);
+        pIONetDescriptor pBuffersDescriptor =
+            AllocatePairOfBuffersOnInit(pContext, size1, size2, FALSE);
+        if (!pBuffersDescriptor) break;
+
+        if (!AddRxBufferToQueue(pContext, pBuffersDescriptor))
+        {
+            VirtIONetFreeBufferDescriptor(pContext, pBuffersDescriptor);
+            break;
+        }
+
+        InsertTailList(&pContext->NetReceiveBuffers, &pBuffersDescriptor->listEntry);
+
+        pContext->NetNofReceiveBuffers++;
+    }
+
+    pContext->NetMaxReceiveBuffers = pContext->NetNofReceiveBuffers;
+    DPrintf(0, ("[%s] MaxReceiveBuffers %d\n", __FUNCTION__, pContext->NetMaxReceiveBuffers) );
+
+    virtqueue_kick(pContext->NetReceiveQueue);
+
+    return nRet;
+}
+
+static NDIS_STATUS FindNetQueues(PARANDIS_ADAPTER *pContext)
+{
+    struct virtqueue *queues[3];
+    unsigned nvqs = pContext->bHasControlQueue ? 3 : 2;
+    NTSTATUS status;
+
+    // We work with two or three virtqueues, 0 - receive, 1 - send, 2 - control
+    status = virtio_find_queues(
+       &pContext->IODevice,
+       nvqs,
+       queues);
+    if (!NT_SUCCESS(status)) {
+       DPrintf(0, ("[%s] virtio_find_queues failed with %x\n", __FUNCTION__, status));
+       return NTStatusToNdisStatus(status);
+    }
+
+    pContext->NetReceiveQueue = queues[0];
+    pContext->NetSendQueue = queues[1];
+    if (pContext->bHasControlQueue) {
+       pContext->NetControlQueue = queues[2];
+    }
+
+    return NDIS_STATUS_SUCCESS;
+}
+
+// called on PASSIVE upon unsuccessful Init or upon Halt
+static void DeleteNetQueues(PARANDIS_ADAPTER *pContext)
+{
+    virtio_delete_queues(&pContext->IODevice);
+}
+
+/**********************************************************
+Initializes VirtIO buffering and related stuff:
+Allocates RX and TX queues and buffers
+Parameters:
+    context
+Return value:
+    TRUE if both queues are allocated
+***********************************************************/
+static NDIS_STATUS ParaNdis_VirtIONetInit(PARANDIS_ADAPTER *pContext)
+{
+    NDIS_STATUS status;
+    DEBUG_ENTRY(0);
+
+    pContext->ControlData.IsCached = 1;
+    pContext->ControlData.size = 512;
+
+    status = FindNetQueues(pContext);
+    if (status != NDIS_STATUS_SUCCESS) {
+        return status;
+    }
+
+    if (pContext->NetReceiveQueue && pContext->NetSendQueue)
+    {
+        PrepareTransmitBuffers(pContext);
+        PrepareReceiveBuffers(pContext);
+
+        if (pContext->NetControlQueue)
+            ParaNdis_InitialAllocatePhysicalMemory(pContext, &pContext->ControlData);
+        if (!pContext->NetControlQueue || !pContext->ControlData.Virtual)
+        {
+            DPrintf(0, ("[%s] The Control vQueue does not work!\n", __FUNCTION__) );
+            pContext->bHasHardwareFilters = FALSE;
+        }
+        if (pContext->nofFreeTxDescriptors &&
+            pContext->NetMaxReceiveBuffers &&
+            pContext->maxFreeHardwareBuffers)
+        {
+            pContext->sgTxGatherTable = ParaNdis_AllocateMemory(pContext,
+                pContext->maxFreeHardwareBuffers * sizeof(pContext->sgTxGatherTable[0]));
+            if (!pContext->sgTxGatherTable)
+            {
+                DisableBothLSOPermanently(pContext, __FUNCTION__, "Can not allocate SG table");
+            }
+            status = NDIS_STATUS_SUCCESS;
+        }
+    }
+    else
+    {
+        DeleteNetQueues(pContext);
+        status = NDIS_STATUS_RESOURCES;
+    }
+    return status;
+}
+
+static void VirtIODeviceRemoveStatus(VirtIODevice *vdev, u8 status)
+{
+    virtio_set_status(
+        vdev,
+        virtio_get_status(vdev) & ~status);
+}
+
+/**********************************************************
+Finishes initialization of context structure, calling also version dependent part
+If this procedure failed, ParaNdis_CleanupContext must be called
+Parameters:
+    context
+Return value:
+    SUCCESS or some kind of failure
+***********************************************************/
+NDIS_STATUS ParaNdis_FinishInitialization(PARANDIS_ADAPTER *pContext)
+{
+    NDIS_STATUS status = NDIS_STATUS_SUCCESS;
+    DEBUG_ENTRY(0);
+
+    NdisAllocateSpinLock(&pContext->SendLock);
+#if !defined(UNIFY_LOCKS)
+    NdisAllocateSpinLock(&pContext->ReceiveLock);
+#endif
+
+    InitializeListHead(&pContext->NetReceiveBuffers);
+    InitializeListHead(&pContext->NetReceiveBuffersWaiting);
+    InitializeListHead(&pContext->NetSendBuffersInUse);
+    InitializeListHead(&pContext->NetFreeSendBuffers);
+
+    status = ParaNdis_FinishSpecificInitialization(pContext);
+
+    if (status == NDIS_STATUS_SUCCESS)
+    {
+        status = ParaNdis_VirtIONetInit(pContext);
+    }
+
+    pContext->Limits.nReusedRxBuffers = pContext->NetMaxReceiveBuffers / 4 + 1;
+
+    if (status == NDIS_STATUS_SUCCESS)
+    {
+        JustForCheckClearInterrupt(pContext, "start 3");
+        pContext->bEnableInterruptHandlingDPC = TRUE;
+        ParaNdis_SetPowerState(pContext, NdisDeviceStateD0);
+        virtio_device_ready(&pContext->IODevice);
+        JustForCheckClearInterrupt(pContext, "start 4");
+        ParaNdis_UpdateDeviceFilters(pContext);
+    }
+    else
+    {
+        virtio_add_status(&pContext->IODevice, VIRTIO_CONFIG_S_FAILED);
+    }
+    DEBUG_EXIT_STATUS(0, status);
+    return status;
+}
+
+/**********************************************************
+Releases VirtIO related resources - queues and buffers
+Parameters:
+    context
+Return value:
+***********************************************************/
+static void VirtIONetRelease(PARANDIS_ADAPTER *pContext)
+{
+    BOOLEAN b;
+    DEBUG_ENTRY(0);
+
+    /* list NetReceiveBuffersWaiting must be free */
+    do
+    {
+        NdisAcquireSpinLock(&pContext->ReceiveLock);
+        b = !IsListEmpty(&pContext->NetReceiveBuffersWaiting);
+        NdisReleaseSpinLock(&pContext->ReceiveLock);
+        if (b)
+        {
+            DPrintf(0, ("[%s] There are waiting buffers", __FUNCTION__));
+            PrintStatistics(pContext);
+            NdisMSleep(5000000);
+        }
+    }while (b);
+
+    DeleteNetQueues(pContext);
+    virtio_device_shutdown(&pContext->IODevice);
+    pContext->bIODeviceInitialized = FALSE;
+
+    /* intentionally commented out
+    FreeDescriptorsFromList(
+        pContext,
+        &pContext->NetReceiveBuffersWaiting,
+        &pContext->ReceiveLock);
+    */
+
+    /* this can be freed, queue shut down */
+    FreeDescriptorsFromList(
+        pContext,
+        &pContext->NetReceiveBuffers,
+        &pContext->ReceiveLock);
+
+    /* this can be freed, queue shut down */
+    FreeDescriptorsFromList(
+        pContext,
+        &pContext->NetSendBuffersInUse,
+        &pContext->SendLock);
+
+    /* this can be freed, send disabled */
+    FreeDescriptorsFromList(
+        pContext,
+        &pContext->NetFreeSendBuffers,
+        &pContext->SendLock);
+
+    if (pContext->ControlData.Virtual)
+        ParaNdis_FreePhysicalMemory(pContext, &pContext->ControlData);
+
+    PrintStatistics(pContext);
+    if (pContext->sgTxGatherTable)
+    {
+        NdisFreeMemory(pContext->sgTxGatherTable, 0, 0);
+    }
+}
+
+static void PreventDPCServicing(PARANDIS_ADAPTER *pContext)
+{
+    LONG inside;;
+    pContext->bEnableInterruptHandlingDPC = FALSE;
+    do
+    {
+        inside = InterlockedIncrement(&pContext->counterDPCInside);
+        InterlockedDecrement(&pContext->counterDPCInside);
+        if (inside > 1)
+        {
+            DPrintf(0, ("[%s] waiting!", __FUNCTION__));
+            NdisMSleep(20000);
+        }
+    } while (inside > 1);
+}
+
+/**********************************************************
+Frees all the resources allocated when the context initialized,
+    calling also version-dependent part
+Parameters:
+    context
+***********************************************************/
+VOID ParaNdis_CleanupContext(PARANDIS_ADAPTER *pContext)
+{
+    UINT i;
+
+    /* disable any interrupt generation */
+    if (pContext->IODevice.addr)
+    {
+        //int nActive;
+        //nActive = virtio_read_isr_status(&pContext->IODevice);
+        /* back compat - remove the OK flag only in legacy mode */
+        VirtIODeviceRemoveStatus(&pContext->IODevice, VIRTIO_CONFIG_S_DRIVER_OK);
+        JustForCheckClearInterrupt(pContext, "exit 1");
+        //nActive += virtio_read_isr_status(&pContext->IODevice);
+        //nActive += virtio_read_isr_status(&pContext->IODevice);
+        //DPrintf(0, ("cleanup %d", nActive));
+    }
+
+    PreventDPCServicing(pContext);
+
+    /****************************************
+    ensure all the incoming packets returned,
+    free all the buffers and their descriptors
+    *****************************************/
+
+    if (pContext->bIODeviceInitialized)
+    {
+        JustForCheckClearInterrupt(pContext, "exit 2");
+        ParaNdis_ResetVirtIONetDevice(pContext);
+        JustForCheckClearInterrupt(pContext, "exit 3");
+    }
+
+    ParaNdis_SetPowerState(pContext, NdisDeviceStateD3);
+    VirtIONetRelease(pContext);
+
+    ParaNdis_FinalizeCleanup(pContext);
+
+    if (pContext->SendLock.SpinLock)
+    {
+        NdisFreeSpinLock(&pContext->SendLock);
+    }
+
+#if !defined(UNIFY_LOCKS)
+    if (pContext->ReceiveLock.SpinLock)
+    {
+        NdisFreeSpinLock(&pContext->ReceiveLock);
+    }
+#endif
+
+    /* free queue shared memory */
+    for (i = 0; i < MAX_NUM_OF_QUEUES; i++) {
+        if (pContext->SharedMemoryRanges[i].pBase != NULL) {
+            NdisMFreeSharedMemory(
+                pContext->MiniportHandle,
+                pContext->SharedMemoryRanges[i].uLength,
+                TRUE /* Cached */,
+                pContext->SharedMemoryRanges[i].pBase,
+                pContext->SharedMemoryRanges[i].BasePA);
+            pContext->SharedMemoryRanges[i].pBase = NULL;
+        }
+    }
+
+    /* unmap our port and memory IO resources */
+    for (i = 0; i < PCI_TYPE0_ADDRESSES; i++)
+    {
+       tBusResource *pRes = &pContext->AdapterResources.PciBars[i];
+       if (pRes->pBase != NULL)
+       {
+          if (pRes->bPortSpace)
+          {
+             NdisMDeregisterIoPortRange(
+                pContext->MiniportHandle,
+                pRes->BasePA.LowPart,
+                pRes->uLength,
+                pRes->pBase);
+          }
+          else
+          {
+             NdisMUnmapIoSpace(
+                pContext->MiniportHandle,
+                pRes->pBase,
+                pRes->uLength);
+          }
+       }
+    }
+}
+
+
+/**********************************************************
+System shutdown handler (shutdown, restart, bugcheck)
+Parameters:
+    context
+***********************************************************/
+VOID ParaNdis_OnShutdown(PARANDIS_ADAPTER *pContext)
+{
+    DEBUG_ENTRY(0); // this is only for kdbg :)
+    ParaNdis_ResetVirtIONetDevice(pContext);
+}
+
+/**********************************************************
+Handles hardware interrupt
+Parameters:
+    context
+    ULONG knownInterruptSources - bitmask of
+Return value:
+    TRUE, if it is our interrupt
+    sets *pRunDpc to TRUE if the DPC should be fired
+***********************************************************/
+BOOLEAN ParaNdis_OnLegacyInterrupt(
+    PARANDIS_ADAPTER *pContext,
+    OUT BOOLEAN *pRunDpc)
+{
+    ULONG status = virtio_read_isr_status(&pContext->IODevice);
+
+    if((status == 0)                                   ||
+       (status == VIRTIO_NET_INVALID_INTERRUPT_STATUS) ||
+       (pContext->powerState != NdisDeviceStateD0))
+    {
+        *pRunDpc = FALSE;
+        return FALSE;
+    }
+
+    PARANDIS_STORE_LAST_INTERRUPT_TIMESTAMP(pContext);
+    ParaNdis_VirtIODisableIrqSynchronized(pContext, isAny);
+    InterlockedOr(&pContext->InterruptStatus, (LONG) ((status & isControl) | isReceive | isTransmit));
+    *pRunDpc = TRUE;
+    return TRUE;
+}
+
+BOOLEAN ParaNdis_OnQueuedInterrupt(
+    PARANDIS_ADAPTER *pContext,
+    OUT BOOLEAN *pRunDpc,
+    ULONG knownInterruptSources)
+{
+    struct virtqueue* _vq = ParaNdis_GetQueueForInterrupt(pContext, knownInterruptSources);
+
+    /* If interrupts for this queue disabled do nothing */
+    if((_vq != NULL) && !ParaNDIS_IsQueueInterruptEnabled(_vq))
+    {
+        *pRunDpc = FALSE;
+    }
+    else
+    {
+        PARANDIS_STORE_LAST_INTERRUPT_TIMESTAMP(pContext);
+        InterlockedOr(&pContext->InterruptStatus, (LONG)knownInterruptSources);
+        ParaNdis_VirtIODisableIrqSynchronized(pContext, knownInterruptSources);
+        *pRunDpc = TRUE;
+    }
+
+    return *pRunDpc;
+}
+
+
+/**********************************************************
+It is called from Rx processing routines in regular mode of operation.
+Returns received buffer back to VirtIO queue, inserting it to NetReceiveBuffers.
+If needed, signals end of RX pause operation
+
+Must be called with &pContext->ReceiveLock acquired
+
+Parameters:
+    context
+    void *pDescriptor - pIONetDescriptor to return
+***********************************************************/
+void ReuseReceiveBufferRegular(PARANDIS_ADAPTER *pContext, pIONetDescriptor pBuffersDescriptor)
+{
+    DEBUG_ENTRY(4);
+
+    if(!pBuffersDescriptor)
+        return;
+
+    RemoveEntryList(&pBuffersDescriptor->listEntry);
+
+    if(AddRxBufferToQueue(pContext, pBuffersDescriptor))
+    {
+        InsertTailList(&pContext->NetReceiveBuffers, &pBuffersDescriptor->listEntry);
+
+        pContext->NetNofReceiveBuffers++;
+
+        if (pContext->NetNofReceiveBuffers > pContext->NetMaxReceiveBuffers)
+        {
+            DPrintf(0, (" Error: NetNofReceiveBuffers > NetMaxReceiveBuffers(%d>%d)",
+                pContext->NetNofReceiveBuffers, pContext->NetMaxReceiveBuffers));
+        }
+
+        if (++pContext->Counters.nReusedRxBuffers >= pContext->Limits.nReusedRxBuffers)
+        {
+            pContext->Counters.nReusedRxBuffers = 0;
+            virtqueue_kick_always(pContext->NetReceiveQueue);
+        }
+
+        if (IsListEmpty(&pContext->NetReceiveBuffersWaiting))
+        {
+            if (pContext->ReceiveState == srsPausing || pContext->ReceivePauseCompletionProc)
+            {
+                ONPAUSECOMPLETEPROC callback = pContext->ReceivePauseCompletionProc;
+                pContext->ReceiveState = srsDisabled;
+                pContext->ReceivePauseCompletionProc = NULL;
+                ParaNdis_DebugHistory(pContext, hopInternalReceivePause, NULL, 0, 0, 0);
+                if (callback) callback(pContext);
+            }
+        }
+    }
+    else
+    {
+        DPrintf(0, ("FAILED TO REUSE THE BUFFER!!!!"));
+        VirtIONetFreeBufferDescriptor(pContext, pBuffersDescriptor);
+        pContext->NetMaxReceiveBuffers--;
+    }
+}
+
+/**********************************************************
+It is called from Rx processing routines between power off and power on in non-paused mode (Win8).
+Returns received buffer to NetReceiveBuffers. 
+All the buffers will be placed into Virtio queue during power-on procedure
+
+Must be called with &pContext->ReceiveLock acquired
+
+Parameters:
+    context
+    void *pDescriptor - pIONetDescriptor to return
+***********************************************************/
+static void ReuseReceiveBufferPowerOff(PARANDIS_ADAPTER *pContext, pIONetDescriptor pBuffersDescriptor)
+{
+    RemoveEntryList(&pBuffersDescriptor->listEntry);
+    InsertTailList(&pContext->NetReceiveBuffers, &pBuffersDescriptor->listEntry);
+}
+
+/**********************************************************
+It is called from Tx processing routines
+Gets all the finished buffer from VirtIO TX path and
+returns them to NetFreeSendBuffers
+
+Must be called with &pContext->SendLock acquired
+
+Parameters:
+    context
+Return value:
+    (for reference) number of TX buffers returned
+***********************************************************/
+UINT ParaNdis_VirtIONetReleaseTransmitBuffers(
+    PARANDIS_ADAPTER *pContext)
+{
+    UINT len, i = 0;
+    pIONetDescriptor pBufferDescriptor;
+
+    DEBUG_ENTRY(4);
+
+    while(NULL != (pBufferDescriptor = virtqueue_get_buf(pContext->NetSendQueue, &len)))
+    {
+        RemoveEntryList(&pBufferDescriptor->listEntry);
+        pContext->nofFreeTxDescriptors++;
+        if (!pBufferDescriptor->nofUsedBuffers)
+        {
+            DPrintf(0, ("[%s] ERROR: nofUsedBuffers not set!", __FUNCTION__));
+        }
+        pContext->nofFreeHardwareBuffers += pBufferDescriptor->nofUsedBuffers;
+        ParaNdis_OnTransmitBufferReleased(pContext, pBufferDescriptor);
+        InsertTailList(&pContext->NetFreeSendBuffers, &pBufferDescriptor->listEntry);
+        DPrintf(3, ("[%s] Free Tx: desc %d, buff %d", __FUNCTION__, pContext->nofFreeTxDescriptors, pContext->nofFreeHardwareBuffers));
+        pBufferDescriptor->nofUsedBuffers = 0;
+        ++i;
+    }
+    if (i)
+    {
+        NdisGetCurrentSystemTime(&pContext->LastTxCompletionTimeStamp);
+        pContext->bDoKickOnNoBuffer = TRUE;
+        pContext->nDetectedStoppedTx = 0;
+    }
+    DEBUG_EXIT_STATUS((i ? 3 : 5), i);
+    return i;
+}
+
+static ULONG FORCEINLINE QueryTcpHeaderOffset(PVOID packetData, ULONG ipHeaderOffset, ULONG ipPacketLength)
+{
+    ULONG res;
+    tTcpIpPacketParsingResult ppr = ParaNdis_ReviewIPPacket(
+        (PUCHAR)packetData + ipHeaderOffset,
+        ipPacketLength,
+        __FUNCTION__);
+    if (ppr.xxpStatus == ppresXxpKnown)
+    {
+        res = ipHeaderOffset + ppr.ipHeaderSize;
+    }
+    else
+    {
+        DPrintf(0, ("[%s] ERROR: NOT a TCP or UDP packet - expected troubles!", __FUNCTION__));
+        res = 0;
+    }
+    return res;
+}
+
+
+/*********************************************************
+Called with from ProcessTx routine with TxLock held
+Uses pContext->sgTxGatherTable
+***********************************************************/
+tCopyPacketResult ParaNdis_DoSubmitPacket(PARANDIS_ADAPTER *pContext, tTxOperationParameters *Params)
+{
+    tCopyPacketResult result;
+    tMapperResult mapResult = {0,0,0};
+    // populating priority tag or LSO MAY require additional SG element
+    UINT nRequiredBuffers;
+    BOOLEAN bUseCopy = FALSE;
+    struct VirtIOBufferDescriptor *sg = pContext->sgTxGatherTable;
+
+    nRequiredBuffers = Params->nofSGFragments + 1 + ((Params->flags & (pcrPriorityTag | pcrLSO)) ? 1 : 0);
+
+    result.size = 0;
+    result.error = cpeOK;
+    if (!pContext->bUseScatterGather ||         // only copy available
+        Params->nofSGFragments == 0 ||          // theoretical case
+        !sg ||                                  // only copy available
+        ((~Params->flags & pcrLSO) && nRequiredBuffers > pContext->maxFreeHardwareBuffers) // to many fragments and normal size of packet
+        )
+    {
+        nRequiredBuffers = 2;
+        bUseCopy = TRUE;
+    }
+    else if (pContext->bUseIndirect && !(Params->flags & pcrNoIndirect))
+    {
+        nRequiredBuffers = 1;
+    }
+
+    // I do not think this will help, but at least we can try freeing some buffers right now
+    if (pContext->nofFreeHardwareBuffers < nRequiredBuffers || !pContext->nofFreeTxDescriptors)
+    {
+        ParaNdis_VirtIONetReleaseTransmitBuffers(pContext);
+    }
+
+    if (nRequiredBuffers > pContext->maxFreeHardwareBuffers)
+    {
+        // LSO and too many buffers, impossible to send
+        result.error = cpeTooLarge;
+        DPrintf(0, ("[%s] ERROR: too many fragments(%d required, %d max.avail)!", __FUNCTION__,
+            nRequiredBuffers, pContext->maxFreeHardwareBuffers));
+    }
+    else if (pContext->nofFreeHardwareBuffers < nRequiredBuffers || !pContext->nofFreeTxDescriptors)
+    {
+        virtqueue_enable_cb_delayed(pContext->NetSendQueue);
+        result.error = cpeNoBuffer;
+    }
+    else if (Params->offloadMss && bUseCopy)
+    {
+        result.error = cpeInternalError;
+        DPrintf(0, ("[%s] ERROR: expecting SG for TSO! (%d buffers, %d bytes)", __FUNCTION__,
+            Params->nofSGFragments, Params->ulDataSize));
+    }
+    else if (bUseCopy)
+    {
+        result = ParaNdis_DoCopyPacketData(pContext, Params);
+    }
+    else
+    {
+        UINT nMappedBuffers;
+        ULONGLONG paOfIndirectArea = 0;
+        PVOID vaOfIndirectArea = NULL;
+        pIONetDescriptor pBuffersDescriptor = (pIONetDescriptor)RemoveHeadList(&pContext->NetFreeSendBuffers);
+        pContext->nofFreeTxDescriptors--;
+        NdisZeroMemory(pBuffersDescriptor->HeaderInfo.Virtual, pBuffersDescriptor->HeaderInfo.size);
+        sg[0].physAddr = pBuffersDescriptor->HeaderInfo.Physical;
+        sg[0].length = pBuffersDescriptor->HeaderInfo.size;
+        ParaNdis_PacketMapper(
+            pContext,
+            Params->packet,
+            Params->ReferenceValue,
+            sg + 1,
+            pBuffersDescriptor,
+            &mapResult);
+        nMappedBuffers = mapResult.usBuffersMapped;
+        if (nMappedBuffers)
+        {
+            nMappedBuffers++;
+            if (pContext->bUseIndirect && !(Params->flags & pcrNoIndirect))
+            {
+                ULONG space1 = (mapResult.usBufferSpaceUsed + 7) & ~7;
+                ULONG space2 = nMappedBuffers * SIZE_OF_SINGLE_INDIRECT_DESC;
+                if (pBuffersDescriptor->DataInfo.size >= (space1 + space2))
+                {
+                    vaOfIndirectArea = RtlOffsetToPointer(pBuffersDescriptor->DataInfo.Virtual, space1);
+                    paOfIndirectArea = pBuffersDescriptor->DataInfo.Physical.QuadPart + space1;
+                    pContext->extraStatistics.framesIndirect++;
+                }
+                else if (nMappedBuffers <= pContext->nofFreeHardwareBuffers)
+                {
+                    // send as is, no indirect
+                }
+                else
+                {
+                    result.error = cpeNoIndirect;
+                    DPrintf(0, ("[%s] Unexpected ERROR of placement!", __FUNCTION__));
+                }
+            }
+            if (result.error == cpeOK)
+            {
+                if (Params->flags & (pcrTcpChecksum | pcrUdpChecksum))
+                {
+                    unsigned short addPriorityLen = (Params->flags & pcrPriorityTag) ? ETH_PRIORITY_HEADER_SIZE : 0;
+                    if (pContext->bDoHardwareChecksum)
+                    {
+                        virtio_net_hdr_basic *pheader = pBuffersDescriptor->HeaderInfo.Virtual;
+                        pheader->flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
+                        if (!Params->tcpHeaderOffset)
+                        {
+                            Params->tcpHeaderOffset = QueryTcpHeaderOffset(
+                                pBuffersDescriptor->DataInfo.Virtual,
+                                pContext->Offload.ipHeaderOffset + addPriorityLen,
+                                mapResult.usBufferSpaceUsed - pContext->Offload.ipHeaderOffset - addPriorityLen);
+                        }
+                        else
+                        {
+                            Params->tcpHeaderOffset += addPriorityLen;
+                        }
+                        pheader->csum_start = (USHORT)Params->tcpHeaderOffset;
+                        pheader->csum_offset = (Params->flags & pcrTcpChecksum) ? TCP_CHECKSUM_OFFSET : UDP_CHECKSUM_OFFSET;
+                    }
+                    else
+                    {
+                        // emulation mode - it is slow and intended only for test of flows
+                        // and debugging of WLK test cases
+                        PVOID pCopy = ParaNdis_AllocateMemory(pContext, Params->ulDataSize);
+                        if (pCopy)
+                        {
+                            tTcpIpPacketParsingResult ppr;
+                            // duplicate entire packet
+                            ParaNdis_PacketCopier(Params->packet, pCopy, Params->ulDataSize, Params->ReferenceValue, FALSE);
+                            // calculate complete TCP/UDP checksum
+                            ppr = ParaNdis_CheckSumVerify(
+                                RtlOffsetToPointer(pCopy, pContext->Offload.ipHeaderOffset + addPriorityLen),
+                                Params->ulDataSize - pContext->Offload.ipHeaderOffset - addPriorityLen,
+                                pcrAnyChecksum | pcrFixXxpChecksum,
+                                __FUNCTION__);
+                            // data portion in aside buffer contains complete IP+TCP header
+                            // rewrite copy of original buffer by one new with calculated data
+                            NdisMoveMemory(
+                                pBuffersDescriptor->DataInfo.Virtual,
+                                pCopy,
+                                mapResult.usBufferSpaceUsed);
+                            NdisFreeMemory(pCopy, 0, 0);
+                        }
+                    }
+                }
+
+                if (0 <= virtqueue_add_buf(
+                    pContext->NetSendQueue,
+                    sg,
+                    nMappedBuffers,
+                    0,
+                    pBuffersDescriptor,
+                    vaOfIndirectArea,
+                    paOfIndirectArea))
+                {
+                    pBuffersDescriptor->nofUsedBuffers = nMappedBuffers;
+                    pContext->nofFreeHardwareBuffers -= nMappedBuffers;
+                    if (pContext->minFreeHardwareBuffers > pContext->nofFreeHardwareBuffers)
+                        pContext->minFreeHardwareBuffers = pContext->nofFreeHardwareBuffers;
+                    pBuffersDescriptor->ReferenceValue = Params->ReferenceValue;
+                    result.size = Params->ulDataSize;
+                    DPrintf(2, ("[%s] Submitted %d buffers (%d bytes), avail %d desc, %d bufs",
+                        __FUNCTION__, nMappedBuffers, result.size,
+                        pContext->nofFreeTxDescriptors, pContext->nofFreeHardwareBuffers
+                    ));
+                }
+                else
+                {
+                    result.error = cpeInternalError;
+                    DPrintf(0, ("[%s] Unexpected ERROR adding buffer to TX engine!..", __FUNCTION__));
+                }
+            }
+        }
+        else
+        {
+            DPrintf(0, ("[%s] Unexpected ERROR: packet not mapped!", __FUNCTION__));
+            result.error = cpeInternalError;
+        }
+
+        if (result.error == cpeOK)
+        {
+            UCHAR ethernetHeader[sizeof(ETH_HEADER)];
+            eInspectedPacketType packetType;
+            /* get the ethernet header for review */
+            ParaNdis_PacketCopier(Params->packet, ethernetHeader, sizeof(ethernetHeader), Params->ReferenceValue, TRUE);
+            packetType = QueryPacketType(ethernetHeader);
+            DebugDumpPacket("sending", ethernetHeader, 3);
+            InsertTailList(&pContext->NetSendBuffersInUse, &pBuffersDescriptor->listEntry);
+            pContext->Statistics.ifHCOutOctets += result.size;
+            switch (packetType)
+            {
+                case iptBroadcast:
+                    pContext->Statistics.ifHCOutBroadcastOctets += result.size;
+                    pContext->Statistics.ifHCOutBroadcastPkts++;
+                    break;
+                case iptMulticast:
+                    pContext->Statistics.ifHCOutMulticastOctets += result.size;
+                    pContext->Statistics.ifHCOutMulticastPkts++;
+                    break;
+                default:
+                    pContext->Statistics.ifHCOutUcastOctets += result.size;
+                    pContext->Statistics.ifHCOutUcastPkts++;
+                    break;
+            }
+
+            if (Params->flags & pcrLSO)
+                pContext->extraStatistics.framesLSO++;
+        }
+        else
+        {
+            pContext->nofFreeTxDescriptors++;
+            InsertHeadList(&pContext->NetFreeSendBuffers, &pBuffersDescriptor->listEntry);
+        }
+    }
+    if (result.error == cpeNoBuffer && pContext->bDoKickOnNoBuffer)
+    {
+        virtqueue_kick_always(pContext->NetSendQueue);
+        pContext->bDoKickOnNoBuffer = FALSE;
+    }
+    if (result.error == cpeOK)
+    {
+        if (Params->flags & (pcrTcpChecksum | pcrUdpChecksum))
+            pContext->extraStatistics.framesCSOffload++;
+    }
+    return result;
+}
+
+
+/**********************************************************
+It is called from Tx processing routines
+Prepares the VirtIO buffer and copies to it the data from provided packet
+
+Must be called with &pContext->SendLock acquired
+
+Parameters:
+    context
+    tPacketType packet          specific type is NDIS dependent
+    tCopyPacketDataFunction     PacketCopier procedure for NDIS-specific type of packet
+Return value:
+    (for reference) number of TX buffers returned
+***********************************************************/
+tCopyPacketResult ParaNdis_DoCopyPacketData(
+    PARANDIS_ADAPTER *pContext,
+    tTxOperationParameters *pParams)
+{
+    tCopyPacketResult result;
+    tCopyPacketResult CopierResult;
+    struct VirtIOBufferDescriptor sg[2];
+    pIONetDescriptor pBuffersDescriptor = NULL;
+    ULONG flags = pParams->flags;
+    UINT nRequiredHardwareBuffers = 2;
+    result.size  = 0;
+    result.error = cpeOK;
+    if (pContext->nofFreeHardwareBuffers < nRequiredHardwareBuffers ||
+        IsListEmpty(&pContext->NetFreeSendBuffers))
+    {
+        result.error = cpeNoBuffer;
+    }
+    if(result.error == cpeOK)
+    {
+        pBuffersDescriptor = (pIONetDescriptor)RemoveHeadList(&pContext->NetFreeSendBuffers);
+        NdisZeroMemory(pBuffersDescriptor->HeaderInfo.Virtual, pBuffersDescriptor->HeaderInfo.size);
+        sg[0].physAddr = pBuffersDescriptor->HeaderInfo.Physical;
+        sg[0].length = pBuffersDescriptor->HeaderInfo.size;
+        sg[1].physAddr = pBuffersDescriptor->DataInfo.Physical;
+        CopierResult = ParaNdis_PacketCopier(
+            pParams->packet,
+            pBuffersDescriptor->DataInfo.Virtual,
+            pBuffersDescriptor->DataInfo.size,
+            pParams->ReferenceValue,
+            FALSE);
+        sg[1].length = result.size = CopierResult.size;
+        // did NDIS ask us to compute CS?
+        if ((flags & (pcrTcpChecksum | pcrUdpChecksum | pcrIpChecksum)) != 0)
+        {
+            // we asked
+            unsigned short addPriorityLen = (pParams->flags & pcrPriorityTag) ? ETH_PRIORITY_HEADER_SIZE : 0;
+            PVOID ipPacket = RtlOffsetToPointer(
+                pBuffersDescriptor->DataInfo.Virtual, pContext->Offload.ipHeaderOffset + addPriorityLen);
+            ULONG ipPacketLength = CopierResult.size - pContext->Offload.ipHeaderOffset - addPriorityLen;
+            if (!pParams->tcpHeaderOffset &&
+                (flags & (pcrTcpChecksum | pcrUdpChecksum)) )
+            {
+                pParams->tcpHeaderOffset = QueryTcpHeaderOffset(
+                    pBuffersDescriptor->DataInfo.Virtual,
+                    pContext->Offload.ipHeaderOffset + addPriorityLen,
+                    ipPacketLength);
+            }
+            else
+            {
+                pParams->tcpHeaderOffset += addPriorityLen;
+            }
+
+            if (pContext->bDoHardwareChecksum)
+            {
+                if (flags & (pcrTcpChecksum | pcrUdpChecksum))
+                {
+                    // hardware offload
+                    virtio_net_hdr_basic *pvnh = (virtio_net_hdr_basic *)pBuffersDescriptor->HeaderInfo.Virtual;
+                    pvnh->csum_start = (USHORT)pParams->tcpHeaderOffset;
+                    pvnh->csum_offset = (flags & pcrTcpChecksum) ? TCP_CHECKSUM_OFFSET : UDP_CHECKSUM_OFFSET;
+                    pvnh->flags |= VIRTIO_NET_HDR_F_NEEDS_CSUM;
+                }
+                if (flags & (pcrIpChecksum))
+                {
+                    ParaNdis_CheckSumVerify(
+                        ipPacket,
+                        ipPacketLength,
+                        pcrIpChecksum | pcrFixIPChecksum,
+                        __FUNCTION__);
+                }
+            }
+            else if (CopierResult.size > pContext->Offload.ipHeaderOffset)
+            {
+                ULONG csFlags = 0;
+                if (flags & pcrIpChecksum) csFlags |= pcrIpChecksum | pcrFixIPChecksum;
+                if (flags & (pcrTcpChecksum | pcrUdpChecksum)) csFlags |= pcrTcpChecksum | pcrUdpChecksum| pcrFixXxpChecksum;
+                // software offload
+                ParaNdis_CheckSumVerify(
+                    ipPacket,
+                    ipPacketLength,
+                    csFlags,
+                    __FUNCTION__);
+            }
+            else
+            {
+                DPrintf(0, ("[%s] ERROR: Invalid buffer size for offload!", __FUNCTION__));
+                result.size = 0;
+                result.error = cpeInternalError;
+            }
+        }
+        pContext->nofFreeTxDescriptors--;
+        if (result.size)
+        {
+            eInspectedPacketType packetType;
+            packetType = QueryPacketType(pBuffersDescriptor->DataInfo.Virtual);
+            DebugDumpPacket("sending", pBuffersDescriptor->DataInfo.Virtual, 3);
+
+            pBuffersDescriptor->nofUsedBuffers = nRequiredHardwareBuffers;
+            pContext->nofFreeHardwareBuffers -= nRequiredHardwareBuffers;
+            if (pContext->minFreeHardwareBuffers > pContext->nofFreeHardwareBuffers)
+                pContext->minFreeHardwareBuffers = pContext->nofFreeHardwareBuffers;
+            if (0 > virtqueue_add_buf(
+                pContext->NetSendQueue,
+                sg,
+                2,
+                0,
+                pBuffersDescriptor,
+                NULL,
+                0
+                ))
+            {
+                pBuffersDescriptor->nofUsedBuffers = 0;
+                pContext->nofFreeHardwareBuffers += nRequiredHardwareBuffers;
+                result.error = cpeInternalError;
+                result.size  = 0;
+                DPrintf(0, ("[%s] Unexpected ERROR adding buffer to TX engine!..", __FUNCTION__));
+            }
+            else
+            {
+                DPrintf(2, ("[%s] Submitted %d buffers (%d bytes), avail %d desc, %d bufs",
+                    __FUNCTION__, nRequiredHardwareBuffers, result.size,
+                    pContext->nofFreeTxDescriptors, pContext->nofFreeHardwareBuffers
+                ));
+            }
+            if (result.error != cpeOK)
+            {
+                InsertTailList(&pContext->NetFreeSendBuffers, &pBuffersDescriptor->listEntry);
+                pContext->nofFreeTxDescriptors++;
+            }
+            else
+            {
+                ULONG reportedSize = pParams->ulDataSize;
+                pBuffersDescriptor->ReferenceValue = pParams->ReferenceValue;
+                InsertTailList(&pContext->NetSendBuffersInUse, &pBuffersDescriptor->listEntry);
+                pContext->Statistics.ifHCOutOctets += reportedSize;
+                switch (packetType)
+                {
+                    case iptBroadcast:
+                        pContext->Statistics.ifHCOutBroadcastOctets += reportedSize;
+                        pContext->Statistics.ifHCOutBroadcastPkts++;
+                        break;
+                    case iptMulticast:
+                        pContext->Statistics.ifHCOutMulticastOctets += reportedSize;
+                        pContext->Statistics.ifHCOutMulticastPkts++;
+                        break;
+                    default:
+                        pContext->Statistics.ifHCOutUcastOctets += reportedSize;
+                        pContext->Statistics.ifHCOutUcastPkts++;
+                        break;
+                }
+            }
+        }
+        else
+        {
+            DPrintf(0, ("[%s] Unexpected ERROR in copying packet data! Continue...", __FUNCTION__));
+            InsertTailList(&pContext->NetFreeSendBuffers, &pBuffersDescriptor->listEntry);
+            pContext->nofFreeTxDescriptors++;
+            // the buffer is not copied and the callback will not be called
+            result.error = cpeInternalError;
+        }
+    }
+
+    return result;
+}
+
+static ULONG ShallPassPacket(PARANDIS_ADAPTER *pContext, PVOID address, UINT len, eInspectedPacketType *pType)
+{
+    ULONG b;
+    if (len <= sizeof(ETH_HEADER)) return FALSE;
+    if (len > pContext->MaxPacketSize.nMaxFullSizeHwRx) return FALSE;
+    if (len > pContext->MaxPacketSize.nMaxFullSizeOS && !ETH_HAS_PRIO_HEADER(address)) return FALSE;
+    *pType = QueryPacketType(address);
+    if (pContext->PacketFilter & NDIS_PACKET_TYPE_PROMISCUOUS)  return TRUE;
+
+    switch(*pType)
+    {
+        case iptBroadcast:
+            b = pContext->PacketFilter & NDIS_PACKET_TYPE_BROADCAST;
+            break;
+        case iptMulticast:
+            b = pContext->PacketFilter & NDIS_PACKET_TYPE_ALL_MULTICAST;
+            if (!b && (pContext->PacketFilter & NDIS_PACKET_TYPE_MULTICAST))
+            {
+                UINT i, n = pContext->MulticastData.nofMulticastEntries * ETH_LENGTH_OF_ADDRESS;
+                b = 1;
+                for (i = 0; b && i < n; i += ETH_LENGTH_OF_ADDRESS)
+                {
+                    ETH_COMPARE_NETWORK_ADDRESSES((PUCHAR)address, &pContext->MulticastData.MulticastList[i], &b)
+                }
+                b = !b;
+            }
+            break;
+        default:
+            ETH_COMPARE_NETWORK_ADDRESSES((PUCHAR)address, pContext->CurrentMacAddress, &b);
+            b = !b && (pContext->PacketFilter & NDIS_PACKET_TYPE_DIRECTED);
+            break;
+    }
+    if (!b)
+    {
+        pContext->extraStatistics.framesFilteredOut++;
+    }
+    return b;
+}
+
+void
+ParaNdis_PadPacketReceived(PVOID pDataBuffer, PULONG pLength)
+{
+    // Ethernet standard declares minimal possible packet size
+    // Packets smaller than that must be padded before transfer
+    // Ethernet HW pads packets on transmit, however in our case
+    // some packets do not travel over Ethernet but being routed
+    // guest-to-guest by virtual switch.
+    // In this case padding is not performed and we may
+    // receive packet smaller than minimal allowed size. This is not
+    // a problem for real life scenarios however WHQL/HCK contains
+    // tests that check padding of received packets.
+    // To make these tests happy we have to pad small packets on receive
+
+    //NOTE: This function assumes that VLAN header has been already stripped out
+
+    if(*pLength < ETH_MIN_PACKET_SIZE)
+    {
+        RtlZeroMemory(RtlOffsetToPointer(pDataBuffer, *pLength), ETH_MIN_PACKET_SIZE - *pLength);
+        *pLength = ETH_MIN_PACKET_SIZE;
+    }
+}
+
+/**********************************************************
+Manages RX path, calling NDIS-specific procedure for packet indication
+Parameters:
+    context
+***********************************************************/
+static UINT ParaNdis_ProcessRxPath(PARANDIS_ADAPTER *pContext, ULONG ulMaxPacketsToIndicate)
+{
+    pIONetDescriptor pBuffersDescriptor;
+    UINT len, headerSize = pContext->nVirtioHeaderSize;
+    eInspectedPacketType packetType = iptInvalid;
+    UINT nReceived = 0, nRetrieved = 0, nReported = 0;
+    tPacketIndicationType   *pBatchOfPackets;
+    UINT                    maxPacketsInBatch = pContext->NetMaxReceiveBuffers;
+    pBatchOfPackets = pContext->bBatchReceive ?
+        ParaNdis_AllocateMemory(pContext, maxPacketsInBatch * sizeof(tPacketIndicationType)) : NULL;
+    NdisAcquireSpinLock(&pContext->ReceiveLock);
+    while ((nReported < ulMaxPacketsToIndicate) && NULL != (pBuffersDescriptor = virtqueue_get_buf(pContext->NetReceiveQueue, &len)))
+    {
+        PVOID pDataBuffer = RtlOffsetToPointer(pBuffersDescriptor->DataInfo.Virtual, pContext->bUseMergedBuffers ? pContext->nVirtioHeaderSize : 0);
+        RemoveEntryList(&pBuffersDescriptor->listEntry);
+        InsertTailList(&pContext->NetReceiveBuffersWaiting, &pBuffersDescriptor->listEntry);
+        pContext->NetNofReceiveBuffers--;
+        nRetrieved++;
+        DPrintf(2, ("[%s] retrieved header+%d b.", __FUNCTION__, len - headerSize));
+        DebugDumpPacket("receive", pDataBuffer, 3);
+
+        if( !pContext->bSurprizeRemoved &&
+            ShallPassPacket(pContext, pDataBuffer, len - headerSize, &packetType) &&
+            pContext->ReceiveState == srsEnabled &&
+            pContext->bConnected)
+        {
+            BOOLEAN b = FALSE;
+            ULONG length = len - headerSize;
+            if (!pBatchOfPackets)
+            {
+                NdisReleaseSpinLock(&pContext->ReceiveLock);
+                b = NULL != ParaNdis_IndicateReceivedPacket(
+                    pContext,
+                    pDataBuffer,
+                    &length,
+                    FALSE,
+                    pBuffersDescriptor);
+                NdisAcquireSpinLock(&pContext->ReceiveLock);
+            }
+            else
+            {
+                tPacketIndicationType packet;
+                packet = ParaNdis_IndicateReceivedPacket(
+                    pContext,
+                    pDataBuffer,
+                    &length,
+                    TRUE,
+                    pBuffersDescriptor);
+                b = packet != NULL;
+                if (b) pBatchOfPackets[nReceived] = packet;
+            }
+            if (!b)
+            {
+                pContext->ReuseBufferProc(pContext, pBuffersDescriptor);
+                //only possible reason for that is unexpected Vlan tag
+                //shall I count it as error?
+                pContext->Statistics.ifInErrors++;
+                pContext->Statistics.ifInDiscards++;
+            }
+            else
+            {
+                nReceived++;
+                nReported++;
+                pContext->Statistics.ifHCInOctets += length;
+                switch(packetType)
+                {
+                    case iptBroadcast:
+                        pContext->Statistics.ifHCInBroadcastPkts++;
+                        pContext->Statistics.ifHCInBroadcastOctets += length;
+                        break;
+                    case iptMulticast:
+                        pContext->Statistics.ifHCInMulticastPkts++;
+                        pContext->Statistics.ifHCInMulticastOctets += length;
+                        break;
+                    default:
+                        pContext->Statistics.ifHCInUcastPkts++;
+                        pContext->Statistics.ifHCInUcastOctets += length;
+                        break;
+                }
+                if (pBatchOfPackets && nReceived == maxPacketsInBatch)
+                {
+                    DPrintf(1, ("[%s] received %d buffers of max %d", __FUNCTION__, nReceived, ulMaxPacketsToIndicate));
+                    NdisReleaseSpinLock(&pContext->ReceiveLock);
+                    ParaNdis_IndicateReceivedBatch(pContext, pBatchOfPackets, nReceived);
+                    NdisAcquireSpinLock(&pContext->ReceiveLock);
+                    nReceived = 0;
+                }
+            }
+        }
+        else
+        {
+            // reuse packet, there is no data or the RX is suppressed
+            pContext->ReuseBufferProc(pContext, pBuffersDescriptor);
+        }
+    }
+    ParaNdis_DebugHistory(pContext, hopReceiveStat, NULL, nRetrieved, nReported, pContext->NetNofReceiveBuffers);
+    NdisReleaseSpinLock(&pContext->ReceiveLock);
+    if (nReceived && pBatchOfPackets)
+    {
+        DPrintf(1, ("[%s]%d: received %d buffers of max %d", __FUNCTION__, KeGetCurrentProcessorNumber(), nReceived, ulMaxPacketsToIndicate));
+        ParaNdis_IndicateReceivedBatch(pContext, pBatchOfPackets, nReceived);
+    }
+    if (pBatchOfPackets) NdisFreeMemory(pBatchOfPackets, 0, 0);
+    return nReported;
+}
+
+void ParaNdis_ReportLinkStatus(PARANDIS_ADAPTER *pContext, BOOLEAN bForce)
+{
+    BOOLEAN bConnected = TRUE;
+    if (pContext->bLinkDetectSupported)
+    {
+        USHORT linkStatus = 0;
+        USHORT offset = sizeof(pContext->CurrentMacAddress);
+        // link changed
+        virtio_get_config(&pContext->IODevice, offset, &linkStatus, sizeof(linkStatus));
+        bConnected = (linkStatus & VIRTIO_NET_S_LINK_UP) != 0;
+    }
+    ParaNdis_IndicateConnect(pContext, bConnected, bForce);
+}
+
+static BOOLEAN RestartQueueSynchronously(tSynchronizedContext *SyncContext)
+{
+    struct virtqueue * _vq = (struct virtqueue *) SyncContext->Parameter;
+    bool res = true;
+    if (!virtqueue_enable_cb(_vq))
+    {
+        virtqueue_disable_cb(_vq);
+        res = false;
+    }
+
+    ParaNdis_DebugHistory(SyncContext->pContext, hopDPC, (PVOID)SyncContext->Parameter, 0x20, res, 0);
+    return !res;
+}
+/**********************************************************
+DPC implementation, common for both NDIS
+Parameters:
+    context
+***********************************************************/
+ULONG ParaNdis_DPCWorkBody(PARANDIS_ADAPTER *pContext, ULONG ulMaxPacketsToIndicate)
+{
+    ULONG stillRequiresProcessing = 0;
+    ULONG interruptSources;
+    UINT uIndicatedRXPackets = 0;
+    UINT numOfPacketsToIndicate = min(ulMaxPacketsToIndicate, pContext->uNumberOfHandledRXPacketsInDPC);
+
+    DEBUG_ENTRY(5);
+    if (pContext->bEnableInterruptHandlingDPC)
+    {
+        InterlockedIncrement(&pContext->counterDPCInside);
+        if (pContext->bEnableInterruptHandlingDPC)
+        {
+            BOOLEAN bDoKick = FALSE;
+
+            InterlockedExchange(&pContext->bDPCInactive, 0);
+            interruptSources = InterlockedExchange(&pContext->InterruptStatus, 0);
+            ParaNdis_DebugHistory(pContext, hopDPC, (PVOID)1, interruptSources, 0, 0);
+            if ((interruptSources & isControl) && pContext->bLinkDetectSupported)
+            {
+                ParaNdis_ReportLinkStatus(pContext, FALSE);
+            }
+            if (interruptSources & isTransmit)
+            {
+                bDoKick = ParaNdis_ProcessTx(pContext, TRUE, TRUE);
+            }
+            if (interruptSources & isReceive)
+            {
+                int nRestartResult = 0;
+
+                do
+                {
+                    LONG rxActive = InterlockedIncrement(&pContext->dpcReceiveActive);
+                    if (rxActive == 1)
+                    {
+                        uIndicatedRXPackets += ParaNdis_ProcessRxPath(pContext, numOfPacketsToIndicate - uIndicatedRXPackets);
+                        InterlockedDecrement(&pContext->dpcReceiveActive);
+                        NdisAcquireSpinLock(&pContext->ReceiveLock);
+                        nRestartResult = ParaNdis_SynchronizeWithInterrupt(
+                            pContext, pContext->ulRxMessage, RestartQueueSynchronously, pContext->NetReceiveQueue);
+                        ParaNdis_DebugHistory(pContext, hopDPC, (PVOID)3, nRestartResult, 0, 0);
+                        NdisReleaseSpinLock(&pContext->ReceiveLock);
+                        DPrintf(nRestartResult ? 2 : 6, ("[%s] queue restarted%s", __FUNCTION__, nRestartResult ? "(Rerun)" : "(Done)"));
+
+                        if (uIndicatedRXPackets < numOfPacketsToIndicate)
+                        {
+
+                        }
+                        else if (uIndicatedRXPackets == numOfPacketsToIndicate)
+                        {
+                            DPrintf(1, ("[%s] Breaking Rx loop after %d indications", __FUNCTION__, uIndicatedRXPackets));
+                            ParaNdis_DebugHistory(pContext, hopDPC, (PVOID)4, nRestartResult, 0, 0);
+                            break;
+                        }
+                        else
+                        {
+                            DPrintf(0, ("[%s] Glitch found: %d allowed, %d indicated", __FUNCTION__, numOfPacketsToIndicate, uIndicatedRXPackets));
+                            ParaNdis_DebugHistory(pContext, hopDPC, (PVOID)6, nRestartResult, 0, 0);
+                        }
+                    }
+                    else
+                    {
+                        InterlockedDecrement(&pContext->dpcReceiveActive);
+                        if (!nRestartResult)
+                        {
+                            NdisAcquireSpinLock(&pContext->ReceiveLock);
+                            nRestartResult = ParaNdis_SynchronizeWithInterrupt(
+                                pContext, pContext->ulRxMessage, RestartQueueSynchronously, pContext->NetReceiveQueue);
+                            ParaNdis_DebugHistory(pContext, hopDPC, (PVOID)5, nRestartResult, 0, 0);
+                            NdisReleaseSpinLock(&pContext->ReceiveLock);
+                        }
+                        DPrintf(1, ("[%s] Skip Rx processing no.%d", __FUNCTION__, rxActive));
+                        break;
+                    }
+                } while (nRestartResult);
+
+                if (nRestartResult) stillRequiresProcessing |= isReceive;
+            }
+
+            if (interruptSources & isTransmit)
+            {
+                NdisAcquireSpinLock(&pContext->SendLock);
+                if (ParaNdis_SynchronizeWithInterrupt(pContext, pContext->ulTxMessage, RestartQueueSynchronously, pContext->NetSendQueue))
+                    stillRequiresProcessing |= isTransmit;
+                if(bDoKick)
+                {
+#ifdef PARANDIS_TEST_TX_KICK_ALWAYS
+                    virtqueue_kick_always(pContext->NetSendQueue);
+#else
+                    virtqueue_kick(pContext->NetSendQueue);
+#endif
+                }
+                NdisReleaseSpinLock(&pContext->SendLock);
+            }
+        }
+        InterlockedDecrement(&pContext->counterDPCInside);
+        ParaNdis_DebugHistory(pContext, hopDPC, NULL, stillRequiresProcessing, pContext->nofFreeHardwareBuffers, pContext->nofFreeTxDescriptors);
+    }
+    return stillRequiresProcessing;
+}
+
+/**********************************************************
+Periodically called procedure, checking dpc activity
+If DPC are not running, it does exactly the same that the DPC
+Parameters:
+    context
+***********************************************************/
+static BOOLEAN CheckRunningDpc(PARANDIS_ADAPTER *pContext)
+{
+    BOOLEAN bStopped;
+    BOOLEAN bReportHang = FALSE;
+    bStopped = 0 != InterlockedExchange(&pContext->bDPCInactive, TRUE);
+
+    if (bStopped)
+    {
+        pContext->nDetectedInactivity++;
+        if (pContext->nEnableDPCChecker)
+        {
+            if (pContext->NetTxPacketsToReturn)
+            {
+                DPrintf(0, ("[%s] - NO ACTIVITY!", __FUNCTION__));
+                if (!pContext->Limits.nPrintDiagnostic) PrintStatistics(pContext);
+                if (pContext->nEnableDPCChecker > 1)
+                {
+                    int isrStatus1, isrStatus2;
+                    isrStatus1 = virtio_read_isr_status(&pContext->IODevice);
+                    isrStatus2 = virtio_read_isr_status(&pContext->IODevice);
+                    if (isrStatus1 || isrStatus2)
+                    {
+                        DPrintf(0, ("WARNING: Interrupt status %d=>%d", isrStatus1, isrStatus2));
+                    }
+                }
+                // simulateDPC
+                InterlockedOr(&pContext->InterruptStatus, isAny);
+                ParaNdis_DPCWorkBody(pContext, PARANDIS_UNLIMITED_PACKETS_TO_INDICATE);
+            }
+        }
+    }
+    else
+    {
+        pContext->nDetectedInactivity = 0;
+    }
+
+    NdisAcquireSpinLock(&pContext->SendLock);
+    if (pContext->nofFreeHardwareBuffers != pContext->maxFreeHardwareBuffers)
+    {
+        if (pContext->nDetectedStoppedTx++ > 1)
+        {
+            DPrintf(0, ("[%s] - Suspicious Tx inactivity (%d)!", __FUNCTION__, pContext->nofFreeHardwareBuffers));
+            //bReportHang = TRUE;
+#ifdef DBG_USE_VIRTIO_PCI_ISR_FOR_HOST_REPORT
+            WriteVirtIODeviceByte(pContext->IODevice.isr, 0);
+#endif
+        }
+    }
+    NdisReleaseSpinLock(&pContext->SendLock);
+
+
+    if (pContext->Limits.nPrintDiagnostic &&
+        ++pContext->Counters.nPrintDiagnostic >= pContext->Limits.nPrintDiagnostic)
+    {
+        pContext->Counters.nPrintDiagnostic = 0;
+        // todo - collect more and put out optionally
+        PrintStatistics(pContext);
+    }
+
+    if (pContext->Statistics.ifHCInOctets == pContext->Counters.prevIn)
+    {
+        pContext->Counters.nRxInactivity++;
+        if (pContext->Counters.nRxInactivity >= 10)
+        {
+//#define CRASH_ON_NO_RX
+#if defined(CRASH_ON_NO_RX)
+            ONPAUSECOMPLETEPROC proc = (ONPAUSECOMPLETEPROC)(PVOID)1;
+            proc(pContext);
+#endif
+        }
+    }
+    else
+    {
+        pContext->Counters.nRxInactivity = 0;
+        pContext->Counters.prevIn = pContext->Statistics.ifHCInOctets;
+    }
+    return bReportHang;
+}
+
+/**********************************************************
+Common implementation of periodic poll
+Parameters:
+    context
+Return:
+    TRUE, if reset required
+***********************************************************/
+BOOLEAN ParaNdis_CheckForHang(PARANDIS_ADAPTER *pContext)
+{
+    static int nHangOn = 0;
+    BOOLEAN b = nHangOn >= 3 && nHangOn < 6;
+    DEBUG_ENTRY(3);
+    b |= CheckRunningDpc(pContext);
+    //uncomment to cause 3 consecutive resets
+    //nHangOn++;
+    DEBUG_EXIT_STATUS(b ? 0 : 6, b);
+    return b;
+}
+
+/**********************************************************
+Common handler of multicast address configuration
+Parameters:
+    PVOID Buffer            array of addresses from NDIS
+    ULONG BufferSize        size of incoming buffer
+    PUINT pBytesRead        update on success
+    PUINT pBytesNeeded      update on wrong buffer size
+Return value:
+    SUCCESS or kind of failure
+***********************************************************/
+NDIS_STATUS ParaNdis_SetMulticastList(
+    PARANDIS_ADAPTER *pContext,
+    PVOID Buffer,
+    ULONG BufferSize,
+    PUINT pBytesRead,
+    PUINT pBytesNeeded)
+{
+    NDIS_STATUS status;
+    ULONG length = BufferSize;
+    if (length > sizeof(pContext->MulticastData.MulticastList))
+    {
+        status = NDIS_STATUS_MULTICAST_FULL;
+        *pBytesNeeded = sizeof(pContext->MulticastData.MulticastList);
+    }
+    else if (length % ETH_LENGTH_OF_ADDRESS)
+    {
+        status = NDIS_STATUS_INVALID_LENGTH;
+        *pBytesNeeded = (length / ETH_LENGTH_OF_ADDRESS) * ETH_LENGTH_OF_ADDRESS;
+    }
+    else
+    {
+        NdisZeroMemory(pContext->MulticastData.MulticastList, sizeof(pContext->MulticastData.MulticastList));
+        if (length)
+            NdisMoveMemory(pContext->MulticastData.MulticastList, Buffer, length);
+        pContext->MulticastData.nofMulticastEntries = length / ETH_LENGTH_OF_ADDRESS;
+        DPrintf(1, ("[%s] New multicast list of %d bytes", __FUNCTION__, length));
+        *pBytesRead = length;
+        status = NDIS_STATUS_SUCCESS;
+    }
+    return status;
+}
+
+/**********************************************************
+Callable from synchronized routine or interrupt handler
+to enable or disable Rx and/or Tx interrupt generation
+Parameters:
+    context
+    interruptSource - isReceive, isTransmit
+    b - 1/0 enable/disable
+***********************************************************/
+VOID ParaNdis_VirtIOEnableIrqSynchronized(PARANDIS_ADAPTER *pContext, ULONG interruptSource)
+{
+    if (interruptSource & isTransmit)
+        virtqueue_enable_cb(pContext->NetSendQueue);
+    if (interruptSource & isReceive)
+        virtqueue_enable_cb(pContext->NetReceiveQueue);
+    ParaNdis_DebugHistory(pContext, hopDPC, (PVOID)0x10, interruptSource, TRUE, 0);
+}
+
+VOID ParaNdis_VirtIODisableIrqSynchronized(PARANDIS_ADAPTER *pContext, ULONG interruptSource)
+{
+    if (interruptSource & isTransmit)
+        virtqueue_disable_cb(pContext->NetSendQueue);
+    if (interruptSource & isReceive)
+        virtqueue_disable_cb(pContext->NetReceiveQueue);
+    ParaNdis_DebugHistory(pContext, hopDPC, (PVOID)0x10, interruptSource, FALSE, 0);
+}
+
+/**********************************************************
+Common handler of PnP events
+Parameters:
+Return value:
+***********************************************************/
+VOID ParaNdis_OnPnPEvent(
+    PARANDIS_ADAPTER *pContext,
+    NDIS_DEVICE_PNP_EVENT pEvent,
+    PVOID   pInfo,
+    ULONG   ulSize)
+{
+    const char *pName = "";
+    DEBUG_ENTRY(0);
+#undef MAKECASE
+#define MAKECASE(x) case (x): pName = #x; break;
+    switch (pEvent)
+    {
+        MAKECASE(NdisDevicePnPEventQueryRemoved)
+        MAKECASE(NdisDevicePnPEventRemoved)
+        MAKECASE(NdisDevicePnPEventSurpriseRemoved)
+        MAKECASE(NdisDevicePnPEventQueryStopped)
+        MAKECASE(NdisDevicePnPEventStopped)
+        MAKECASE(NdisDevicePnPEventPowerProfileChanged)
+        default:
+            break;
+    }
+    ParaNdis_DebugHistory(pContext, hopPnpEvent, NULL, pEvent, 0, 0);
+    DPrintf(0, ("[%s] (%s)", __FUNCTION__, pName));
+    if (pEvent == NdisDevicePnPEventSurpriseRemoved)
+    {
+        // on simulated surprise removal (under PnpTest) we need to reset the device
+        // to prevent any access of device queues to memory buffers
+        pContext->bSurprizeRemoved = TRUE;
+        ParaNdis_ResetVirtIONetDevice(pContext);
+    }
+    pContext->PnpEvents[pContext->nPnpEventIndex++] = pEvent;
+    if (pContext->nPnpEventIndex > sizeof(pContext->PnpEvents)/sizeof(pContext->PnpEvents[0]))
+        pContext->nPnpEventIndex = 0;
+}
+
+static BOOLEAN SendControlMessage(
+    PARANDIS_ADAPTER *pContext,
+    UCHAR cls,
+    UCHAR cmd,
+    PVOID buffer1,
+    ULONG size1,
+    PVOID buffer2,
+    ULONG size2,
+    int levelIfOK
+    )
+{
+    BOOLEAN bOK = FALSE;
+    NdisAcquireSpinLock(&pContext->ReceiveLock);
+    if (pContext->ControlData.Virtual && pContext->ControlData.size > (size1 + size2 + 16))
+    {
+        struct VirtIOBufferDescriptor sg[4];
+        PUCHAR pBase = (PUCHAR)pContext->ControlData.Virtual;
+        PHYSICAL_ADDRESS phBase = pContext->ControlData.Physical;
+        ULONG offset = 0;
+        UINT nOut = 1;
+
+        ((virtio_net_ctrl_hdr *)pBase)->class_of_command = cls;
+        ((virtio_net_ctrl_hdr *)pBase)->cmd = cmd;
+        sg[0].physAddr = phBase;
+        sg[0].length = sizeof(virtio_net_ctrl_hdr);
+        offset += sg[0].length;
+        offset = (offset + 3) & ~3;
+        if (size1)
+        {
+            NdisMoveMemory(pBase + offset, buffer1, size1);
+            sg[nOut].physAddr = phBase;
+            sg[nOut].physAddr.QuadPart += offset;
+            sg[nOut].length = size1;
+            offset += size1;
+            offset = (offset + 3) & ~3;
+            nOut++;
+        }
+        if (size2)
+        {
+            NdisMoveMemory(pBase + offset, buffer2, size2);
+            sg[nOut].physAddr = phBase;
+            sg[nOut].physAddr.QuadPart += offset;
+            sg[nOut].length = size2;
+            offset += size2;
+            offset = (offset + 3) & ~3;
+            nOut++;
+        }
+        sg[nOut].physAddr = phBase;
+        sg[nOut].physAddr.QuadPart += offset;
+        sg[nOut].length = sizeof(virtio_net_ctrl_ack);
+        *(virtio_net_ctrl_ack *)(pBase + offset) = VIRTIO_NET_ERR;
+
+        if (0 <= virtqueue_add_buf(pContext->NetControlQueue, sg, nOut, 1, (PVOID)1, NULL, 0))
+        {
+            UINT len;
+            void *p;
+            virtqueue_kick_always(pContext->NetControlQueue);
+            p = virtqueue_get_buf(pContext->NetControlQueue, &len);
+            if (!p)
+            {
+                DPrintf(0, ("%s - ERROR: get_buf failed", __FUNCTION__));
+            }
+            else if (len != sizeof(virtio_net_ctrl_ack))
+            {
+                DPrintf(0, ("%s - ERROR: wrong len %d", __FUNCTION__, len));
+            }
+            else if (*(virtio_net_ctrl_ack *)(pBase + offset) != VIRTIO_NET_OK)
+            {
+                DPrintf(0, ("%s - ERROR: error %d returned", __FUNCTION__, *(virtio_net_ctrl_ack *)(pBase + offset)));
+            }
+            else
+            {
+                // everything is OK
+                DPrintf(levelIfOK, ("%s OK(%d.%d,buffers of %d and %d) ", __FUNCTION__, cls, cmd, size1, size2));
+                bOK = TRUE;
+            }
+        }
+        else
+        {
+            DPrintf(0, ("%s - ERROR: add_buf failed", __FUNCTION__));
+        }
+    }
+    else
+    {
+        DPrintf(0, ("%s (buffer %d,%d) - ERROR: message too LARGE", __FUNCTION__, size1, size2));
+    }
+    NdisReleaseSpinLock(&pContext->ReceiveLock);
+    return bOK;
+}
+
+static VOID ParaNdis_DeviceFiltersUpdateRxMode(PARANDIS_ADAPTER *pContext)
+{
+    u8 val;
+    ULONG f = pContext->PacketFilter;
+    val = (f & NDIS_PACKET_TYPE_ALL_MULTICAST) ? 1 : 0;
+    SendControlMessage(pContext, VIRTIO_NET_CTRL_RX_MODE, VIRTIO_NET_CTRL_RX_MODE_ALLMULTI, &val, sizeof(val), NULL, 0, 2);
+    //SendControlMessage(pContext, VIRTIO_NET_CTRL_RX_MODE, VIRTIO_NET_CTRL_RX_MODE_ALLUNI, &val, sizeof(val), NULL, 0, 2);
+    val = (f & (NDIS_PACKET_TYPE_MULTICAST | NDIS_PACKET_TYPE_ALL_MULTICAST)) ? 0 : 1;
+    SendControlMessage(pContext, VIRTIO_NET_CTRL_RX_MODE, VIRTIO_NET_CTRL_RX_MODE_NOMULTI, &val, sizeof(val), NULL, 0, 2);
+    val = (f & NDIS_PACKET_TYPE_DIRECTED) ? 0 : 1;
+    SendControlMessage(pContext, VIRTIO_NET_CTRL_RX_MODE, VIRTIO_NET_CTRL_RX_MODE_NOUNI, &val, sizeof(val), NULL, 0, 2);
+    val = (f & NDIS_PACKET_TYPE_BROADCAST) ? 0 : 1;
+    SendControlMessage(pContext, VIRTIO_NET_CTRL_RX_MODE, VIRTIO_NET_CTRL_RX_MODE_NOBCAST, &val, sizeof(val), NULL, 0, 2);
+    val = (f & NDIS_PACKET_TYPE_PROMISCUOUS) ? 1 : 0;
+    SendControlMessage(pContext, VIRTIO_NET_CTRL_RX_MODE, VIRTIO_NET_CTRL_RX_MODE_PROMISC, &val, sizeof(val), NULL, 0, 2);
+}
+
+static VOID ParaNdis_DeviceFiltersUpdateAddresses(PARANDIS_ADAPTER *pContext)
+{
+    struct
+    {
+        struct virtio_net_ctrl_mac header;
+        UCHAR addr[ETH_LENGTH_OF_ADDRESS];
+    } uCast;
+    uCast.header.entries = 1;
+    NdisMoveMemory(uCast.addr, pContext->CurrentMacAddress, sizeof(uCast.addr));
+    SendControlMessage(pContext, VIRTIO_NET_CTRL_MAC, VIRTIO_NET_CTRL_MAC_TABLE_SET,
+        &uCast, sizeof(uCast), &pContext->MulticastData,sizeof(pContext->MulticastData.nofMulticastEntries) + pContext->MulticastData.nofMulticastEntries * ETH_ALEN, 2);
+}
+
+static VOID SetSingleVlanFilter(PARANDIS_ADAPTER *pContext, ULONG vlanId, BOOLEAN bOn, int levelIfOK)
+{
+    u16 val = vlanId & 0xfff;
+    UCHAR cmd = bOn ? VIRTIO_NET_CTRL_VLAN_ADD : VIRTIO_NET_CTRL_VLAN_DEL;
+    SendControlMessage(pContext, VIRTIO_NET_CTRL_VLAN, cmd, &val, sizeof(val), NULL, 0, levelIfOK);
+}
+
+static VOID SetAllVlanFilters(PARANDIS_ADAPTER *pContext, BOOLEAN bOn)
+{
+    ULONG i;
+    for (i = 0; i <= MAX_VLAN_ID; ++i)
+        SetSingleVlanFilter(pContext, i, bOn, 7);
+}
+
+/*
+    possible values of filter set (pContext->ulCurrentVlansFilterSet):
+    0 - all disabled
+    1..4095 - one selected enabled
+    4096 - all enabled
+    Note that only 0th vlan can't be enabled
+*/
+VOID ParaNdis_DeviceFiltersUpdateVlanId(PARANDIS_ADAPTER *pContext)
+{
+    if (pContext->bHasHardwareFilters)
+    {
+        ULONG newFilterSet;
+        if (IsVlanSupported(pContext))
+            newFilterSet = pContext->VlanId ? pContext->VlanId : (MAX_VLAN_ID + 1);
+        else
+            newFilterSet = IsPrioritySupported(pContext) ? (MAX_VLAN_ID + 1) : 0;
+        if (newFilterSet != pContext->ulCurrentVlansFilterSet)
+        {
+            if (pContext->ulCurrentVlansFilterSet > MAX_VLAN_ID)
+                SetAllVlanFilters(pContext, FALSE);
+            else if (pContext->ulCurrentVlansFilterSet)
+                SetSingleVlanFilter(pContext, pContext->ulCurrentVlansFilterSet, FALSE, 2);
+
+            pContext->ulCurrentVlansFilterSet = newFilterSet;
+
+            if (pContext->ulCurrentVlansFilterSet > MAX_VLAN_ID)
+                SetAllVlanFilters(pContext, TRUE);
+            else if (pContext->ulCurrentVlansFilterSet)
+                SetSingleVlanFilter(pContext, pContext->ulCurrentVlansFilterSet, TRUE, 2);
+        }
+    }
+}
+
+VOID ParaNdis_UpdateDeviceFilters(PARANDIS_ADAPTER *pContext)
+{
+    if (pContext->bHasHardwareFilters)
+    {
+        ParaNdis_DeviceFiltersUpdateRxMode(pContext);
+        ParaNdis_DeviceFiltersUpdateAddresses(pContext);
+        ParaNdis_DeviceFiltersUpdateVlanId(pContext);
+    }
+}
+
+NDIS_STATUS ParaNdis_PowerOn(PARANDIS_ADAPTER *pContext)
+{
+    LIST_ENTRY TempList;
+    NDIS_STATUS status;
+    DEBUG_ENTRY(0);
+    ParaNdis_DebugHistory(pContext, hopPowerOn, NULL, 1, 0, 0);
+    ParaNdis_ResetVirtIONetDevice(pContext);
+    virtio_add_status(&pContext->IODevice, VIRTIO_CONFIG_S_ACKNOWLEDGE | VIRTIO_CONFIG_S_DRIVER);
+    /* virtio_get_features must be called once upon device initialization:
+     otherwise the device will not work properly */
+    (void)virtio_get_features(&pContext->IODevice);
+
+    if (pContext->bUseMergedBuffers)
+        VirtIODeviceEnableGuestFeature(pContext, VIRTIO_NET_F_MRG_RXBUF);
+    if (VirtIODeviceGetHostFeature(pContext, VIRTIO_RING_F_EVENT_IDX))
+        VirtIODeviceEnableGuestFeature(pContext, VIRTIO_RING_F_EVENT_IDX);
+    if (pContext->bDoGuestChecksumOnReceive)
+        VirtIODeviceEnableGuestFeature(pContext, VIRTIO_NET_F_GUEST_CSUM);
+    if (VirtIODeviceGetHostFeature(pContext, VIRTIO_F_VERSION_1))
+        VirtIODeviceEnableGuestFeature(pContext, VIRTIO_F_VERSION_1);
+    if (VirtIODeviceGetHostFeature(pContext, VIRTIO_F_ANY_LAYOUT))
+        VirtIODeviceEnableGuestFeature(pContext, VIRTIO_F_ANY_LAYOUT);
+
+    status = FinalizeFeatures(pContext);
+    if (status == NDIS_STATUS_SUCCESS) {
+        status = FindNetQueues(pContext);
+    }
+    if (status != NDIS_STATUS_SUCCESS) {
+        virtio_add_status(&pContext->IODevice, VIRTIO_CONFIG_S_FAILED);
+        return status;
+    }
+
+    ParaNdis_RestoreDeviceConfigurationAfterReset(pContext);
+
+    ParaNdis_UpdateDeviceFilters(pContext);
+
+    InitializeListHead(&TempList);
+    
+    /* submit all the receive buffers */
+    NdisAcquireSpinLock(&pContext->ReceiveLock);
+    
+    pContext->ReuseBufferProc = (tReuseReceiveBufferProc)ReuseReceiveBufferRegular;
+    
+    while (!IsListEmpty(&pContext->NetReceiveBuffers))
+    {
+        pIONetDescriptor pBufferDescriptor =
+            (pIONetDescriptor)RemoveHeadList(&pContext->NetReceiveBuffers);
+        InsertTailList(&TempList, &pBufferDescriptor->listEntry);
+    }
+    pContext->NetNofReceiveBuffers = 0;
+    while (!IsListEmpty(&TempList))
+    {
+        pIONetDescriptor pBufferDescriptor =
+            (pIONetDescriptor)RemoveHeadList(&TempList);
+        if (AddRxBufferToQueue(pContext, pBufferDescriptor))
+        {
+            InsertTailList(&pContext->NetReceiveBuffers, &pBufferDescriptor->listEntry);
+            pContext->NetNofReceiveBuffers++;
+        }
+        else
+        {
+            DPrintf(0, ("FAILED TO REUSE THE BUFFER!!!!"));
+            VirtIONetFreeBufferDescriptor(pContext, pBufferDescriptor);
+            pContext->NetMaxReceiveBuffers--;
+        }
+    }
+    virtqueue_kick(pContext->NetReceiveQueue);
+    ParaNdis_SetPowerState(pContext, NdisDeviceStateD0);
+    pContext->bEnableInterruptHandlingDPC = TRUE;
+    virtio_device_ready(&pContext->IODevice);
+    
+    NdisReleaseSpinLock(&pContext->ReceiveLock);
+
+    // if bFastSuspendInProcess is set by Win8 power-off procedure,
+    // the ParaNdis_Resume enables Tx and RX
+    // otherwise it does not do anything in Vista+ (Tx and RX are enabled after power-on by Restart)
+    ParaNdis_Resume(pContext);
+    pContext->bFastSuspendInProcess = FALSE;
+    
+    ParaNdis_ReportLinkStatus(pContext, TRUE);
+    ParaNdis_DebugHistory(pContext, hopPowerOn, NULL, 0, 0, 0);
+
+    return status;
+}
+
+VOID ParaNdis_PowerOff(PARANDIS_ADAPTER *pContext)
+{
+    DEBUG_ENTRY(0);
+    ParaNdis_DebugHistory(pContext, hopPowerOff, NULL, 1, 0, 0);
+
+    ParaNdis_IndicateConnect(pContext, FALSE, FALSE);
+
+    // if bFastSuspendInProcess is set by Win8 power-off procedure
+    // the ParaNdis_Suspend does fast Rx stop without waiting (=>srsPausing, if there are some RX packets in Ndis)
+    pContext->bFastSuspendInProcess = pContext->bNoPauseOnSuspend && pContext->ReceiveState == srsEnabled;
+    ParaNdis_Suspend(pContext);
+    if (pContext->IODevice.addr)
+    {
+        /* back compat - remove the OK flag only in legacy mode */
+        VirtIODeviceRemoveStatus(&pContext->IODevice, VIRTIO_CONFIG_S_DRIVER_OK);
+    }
+    
+    if (pContext->bFastSuspendInProcess)
+    {
+        NdisAcquireSpinLock(&pContext->ReceiveLock);
+        pContext->ReuseBufferProc = (tReuseReceiveBufferProc)ReuseReceiveBufferPowerOff;
+        NdisReleaseSpinLock(&pContext->ReceiveLock);
+    }
+    
+    ParaNdis_SetPowerState(pContext, NdisDeviceStateD3);
+
+    PreventDPCServicing(pContext);
+
+    /*******************************************************************
+        shutdown queues to have all the receive buffers under our control
+        all the transmit buffers move to list of free buffers
+    ********************************************************************/
+
+    NdisAcquireSpinLock(&pContext->SendLock);
+    virtqueue_shutdown(pContext->NetSendQueue);
+    while (!IsListEmpty(&pContext->NetSendBuffersInUse))
+    {
+        pIONetDescriptor pBufferDescriptor =
+            (pIONetDescriptor)RemoveHeadList(&pContext->NetSendBuffersInUse);
+        InsertTailList(&pContext->NetFreeSendBuffers, &pBufferDescriptor->listEntry);
+        pContext->nofFreeTxDescriptors++;
+        pContext->nofFreeHardwareBuffers += pBufferDescriptor->nofUsedBuffers;
+    }
+    NdisReleaseSpinLock(&pContext->SendLock);
+
+    NdisAcquireSpinLock(&pContext->ReceiveLock);
+    virtqueue_shutdown(pContext->NetReceiveQueue);
+    NdisReleaseSpinLock(&pContext->ReceiveLock);
+    if (pContext->NetControlQueue) {
+        virtqueue_shutdown(pContext->NetControlQueue);
+    }
+
+    DPrintf(0, ("WARNING: deleting queues!!!!!!!!!"));
+    DeleteNetQueues(pContext);
+    pContext->NetSendQueue = NULL;
+    pContext->NetReceiveQueue = NULL;
+    pContext->NetControlQueue = NULL;
+
+    ParaNdis_ResetVirtIONetDevice(pContext);
+    ParaNdis_DebugHistory(pContext, hopPowerOff, NULL, 0, 0, 0);
+}
+
+void ParaNdis_CallOnBugCheck(PARANDIS_ADAPTER *pContext)
+{
+    if (pContext->IODevice.isr)
+    {
+#ifdef DBG_USE_VIRTIO_PCI_ISR_FOR_HOST_REPORT
+        WriteVirtIODeviceByte(pContext->IODevice.isr, 1);
+#endif
+    }
+}
+
+tChecksumCheckResult ParaNdis_CheckRxChecksum(PARANDIS_ADAPTER *pContext, ULONG virtioFlags, PVOID pRxPacket, ULONG len)
+{
+    tOffloadSettingsFlags f = pContext->Offload.flags;
+    tChecksumCheckResult res, resIp;
+    PVOID pIpHeader = RtlOffsetToPointer(pRxPacket, ETH_HEADER_SIZE);
+    tTcpIpPacketParsingResult ppr;
+    ULONG flagsToCalculate = 0;
+    res.value = 0;
+    resIp.value = 0;
+
+    //VIRTIO_NET_HDR_F_NEEDS_CSUM - we need to calculate TCP/UDP CS
+    //VIRTIO_NET_HDR_F_DATA_VALID - host tells us TCP/UDP CS is OK
+
+    if (f.fRxIPChecksum) flagsToCalculate |= pcrIpChecksum; // check only
+
+    if (!(virtioFlags & VIRTIO_NET_HDR_F_DATA_VALID))
+    {
+        if (virtioFlags & VIRTIO_NET_HDR_F_NEEDS_CSUM)
+        {
+            flagsToCalculate |= pcrFixXxpChecksum | pcrTcpChecksum | pcrUdpChecksum;
+        }
+        else
+        {
+            if (f.fRxTCPChecksum) flagsToCalculate |= pcrTcpV4Checksum;
+            if (f.fRxUDPChecksum) flagsToCalculate |= pcrUdpV4Checksum;
+            if (f.fRxTCPv6Checksum) flagsToCalculate |= pcrTcpV6Checksum;
+            if (f.fRxUDPv6Checksum) flagsToCalculate |= pcrUdpV6Checksum;
+        }
+    }
+
+    ppr = ParaNdis_CheckSumVerify(pIpHeader, len - ETH_HEADER_SIZE, flagsToCalculate, __FUNCTION__);
+
+    if (virtioFlags & VIRTIO_NET_HDR_F_DATA_VALID)
+    {
+        pContext->extraStatistics.framesRxCSHwOK++;
+        ppr.xxpCheckSum = ppresCSOK;
+    }
+
+    if (ppr.ipStatus == ppresIPV4 && !ppr.IsFragment)
+    {
+        if (f.fRxIPChecksum)
+        {
+            res.flags.IpOK =  ppr.ipCheckSum == ppresCSOK;
+            res.flags.IpFailed = ppr.ipCheckSum == ppresCSBad;
+        }
+        if(ppr.xxpStatus == ppresXxpKnown)
+        {
+            if(ppr.TcpUdp == ppresIsTCP) /* TCP */
+            {
+                if (f.fRxTCPChecksum)
+                {
+                    res.flags.TcpOK = ppr.xxpCheckSum == ppresCSOK || ppr.fixedXxpCS;
+                    res.flags.TcpFailed = !res.flags.TcpOK;
+                }
+            }
+            else /* UDP */
+            {
+                if (f.fRxUDPChecksum)
+                {
+                    res.flags.UdpOK = ppr.xxpCheckSum == ppresCSOK || ppr.fixedXxpCS;
+                    res.flags.UdpFailed = !res.flags.UdpOK;
+                }
+            }
+        }
+    }
+    else if (ppr.ipStatus == ppresIPV6)
+    {
+        if(ppr.xxpStatus == ppresXxpKnown)
+        {
+            if(ppr.TcpUdp == ppresIsTCP) /* TCP */
+            {
+                if (f.fRxTCPv6Checksum)
+                {
+                    res.flags.TcpOK = ppr.xxpCheckSum == ppresCSOK || ppr.fixedXxpCS;
+                    res.flags.TcpFailed = !res.flags.TcpOK;
+                }
+            }
+            else /* UDP */
+            {
+                if (f.fRxUDPv6Checksum)
+                {
+                    res.flags.UdpOK = ppr.xxpCheckSum == ppresCSOK || ppr.fixedXxpCS;
+                    res.flags.UdpFailed = !res.flags.UdpOK;
+                }
+            }
+        }
+    }
+
+    if (pContext->bDoIPCheckRx &&
+        (f.fRxIPChecksum || f.fRxTCPChecksum || f.fRxUDPChecksum || f.fRxTCPv6Checksum || f.fRxUDPv6Checksum))
+    {
+        ppr = ParaNdis_CheckSumVerify(pIpHeader, len - ETH_HEADER_SIZE, pcrAnyChecksum, __FUNCTION__);
+        if (ppr.ipStatus == ppresIPV4 && !ppr.IsFragment)
+        {
+            resIp.flags.IpOK = !!f.fRxIPChecksum && ppr.ipCheckSum == ppresCSOK;
+            resIp.flags.IpFailed = !!f.fRxIPChecksum && ppr.ipCheckSum == ppresCSBad;
+            if (f.fRxTCPChecksum && ppr.xxpStatus == ppresXxpKnown && ppr.TcpUdp == ppresIsTCP)
+            {
+                resIp.flags.TcpOK = ppr.xxpCheckSum == ppresCSOK;
+                resIp.flags.TcpFailed = ppr.xxpCheckSum == ppresCSBad;
+            }
+            if (f.fRxUDPChecksum && ppr.xxpStatus == ppresXxpKnown && ppr.TcpUdp == ppresIsUDP)
+            {
+                resIp.flags.UdpOK = ppr.xxpCheckSum == ppresCSOK;
+                resIp.flags.UdpFailed = ppr.xxpCheckSum == ppresCSBad;
+            }
+        }
+        else if (ppr.ipStatus == ppresIPV6)
+        {
+            if (f.fRxTCPv6Checksum && ppr.xxpStatus == ppresXxpKnown && ppr.TcpUdp == ppresIsTCP)
+            {
+                resIp.flags.TcpOK = ppr.xxpCheckSum == ppresCSOK;
+                resIp.flags.TcpFailed = ppr.xxpCheckSum == ppresCSBad;
+            }
+            if (f.fRxUDPv6Checksum && ppr.xxpStatus == ppresXxpKnown && ppr.TcpUdp == ppresIsUDP)
+            {
+                resIp.flags.UdpOK = ppr.xxpCheckSum == ppresCSOK;
+                resIp.flags.UdpFailed = ppr.xxpCheckSum == ppresCSBad;
+            }
+        }
+
+        if (res.value != resIp.value)
+        {
+            // if HW did not set some bits that IP checker set, it is a mistake:
+            // or GOOD CS is not labeled, or BAD checksum is not labeled
+            tChecksumCheckResult diff;
+            diff.value = resIp.value & ~res.value;
+            if (diff.flags.IpFailed || diff.flags.TcpFailed || diff.flags.UdpFailed)
+                pContext->extraStatistics.framesRxCSHwMissedBad++;
+            if (diff.flags.IpOK || diff.flags.TcpOK || diff.flags.UdpOK)
+                pContext->extraStatistics.framesRxCSHwMissedGood++;
+            if (diff.value)
+            {
+                DPrintf(0, ("[%s] real %X <> %X (virtio %X)", __FUNCTION__, resIp.value, res.value, virtioFlags));
+            }
+            res.value = resIp.value;
+        }
+    }
+
+    return res;
+}