/*
 * 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 = 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 = 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 = 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__"(2)");
        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;
}
