/*
 * This file contains driver-related part of NDIS5.X adapter driver.
 *
 * 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 "ParaNdis5.h"

//#define NO_XP_POWER_MANAGEMENT

#ifdef WPP_EVENT_TRACING
#include "ParaNdis5-Driver.tmh"
#endif

static NDIS_HANDLE      DriverHandle;
static ULONG            gID = 0;

/******************************************************
Unload handler, only responsibility is cleanup WPP
*******************************************************/
DRIVER_UNLOAD ParaVirtualNICUnload;
static VOID ParaVirtualNICUnload(IN  PDRIVER_OBJECT  pDriverObject)
{
    DEBUG_ENTRY(0);
    ParaNdis_DebugCleanup(pDriverObject);
}

/*************************************************************
Required NDIS function
Responsible to put the adapter to known (initial) hardware state

Do not call any NDIS functions
*************************************************************/
static VOID ParaNdis5_Shutdown(IN NDIS_HANDLE MiniportAdapterContext)
{
    PARANDIS_ADAPTER *pContext = (PARANDIS_ADAPTER *)MiniportAdapterContext;
    ParaNdis_OnShutdown(pContext);
}

/******************************************************
Required NDIS procedure
Allocates and initializes adapter context
Finally sets send and receive to Enabled state and reports connect
Returns:
NDIS_STATUS SUCCESS or some error code
*******************************************************/
static NDIS_STATUS ParaNdis5_Initialize(OUT PNDIS_STATUS OpenErrorStatus,
                                    OUT PUINT SelectedMediumIndex,
                                    IN PNDIS_MEDIUM MediumArray,
                                    IN UINT MediumArraySize,
                                    IN NDIS_HANDLE MiniportAdapterHandle,
                                    IN NDIS_HANDLE WrapperConfigurationContext)
{
    NDIS_STATUS  status = NDIS_STATUS_UNSUPPORTED_MEDIA;
    PARANDIS_ADAPTER *pContext = NULL;
    UINT i;
    for(i = 0; i < MediumArraySize; ++i)
    {
        if(MediumArray[i] == NdisMedium802_3)
        {
            *SelectedMediumIndex = i;
            status = NDIS_STATUS_SUCCESS;
            break;
        }
    }

    if (status == NDIS_STATUS_SUCCESS)
    {
        pContext =
            (PARANDIS_ADAPTER *)ParaNdis_AllocateMemory(NULL, sizeof(PARANDIS_ADAPTER));
        if (!pContext)
        {
            status = NDIS_STATUS_RESOURCES;
        }
    }

    if (status == NDIS_STATUS_SUCCESS)
    {
        PVOID pResourceList = &status;
        UINT  uSize = 0;
        NdisZeroMemory(pContext, sizeof(PARANDIS_ADAPTER));
        pContext->ulUniqueID = NdisInterlockedIncrement(&gID);
        pContext->DriverHandle = DriverHandle;
        pContext->MiniportHandle = MiniportAdapterHandle;
        pContext->WrapperConfigurationHandle = WrapperConfigurationContext;
        NdisMQueryAdapterResources(&status, WrapperConfigurationContext, pResourceList, &uSize);
        if (uSize > 0)
            pResourceList = ParaNdis_AllocateMemory(MiniportAdapterHandle, uSize);
        else
            pResourceList = NULL;
        if (!pResourceList)
            status = uSize > 0 ? NDIS_STATUS_RESOURCES : NDIS_STATUS_FAILURE;
        else
        {
            ULONG attributes;
            attributes = NDIS_ATTRIBUTE_DESERIALIZE | NDIS_ATTRIBUTE_BUS_MASTER;
            // in XP SP2, if this flag is NOT set, the NDIS halts miniport
            // upon transition to S1..S4.
            // it seems that XP SP3 ignores it and always sends SET_POWER to D3
#ifndef NO_XP_POWER_MANAGEMENT
            attributes |= NDIS_ATTRIBUTE_NO_HALT_ON_SUSPEND;
#endif
            NdisMSetAttributesEx(
                MiniportAdapterHandle,
                pContext,
                0,
                attributes,
                NdisInterfacePci);
            NdisMQueryAdapterResources(&status, WrapperConfigurationContext, pResourceList, &uSize);
            status = ParaNdis_InitializeContext(pContext, (PNDIS_RESOURCE_LIST)pResourceList);
            NdisFreeMemory(pResourceList, 0, 0);
        }
    }

    if (status == NDIS_STATUS_SUCCESS)
    {
        status = ParaNdis_FinishInitialization(pContext);
        if (status == NDIS_STATUS_SUCCESS)
        {
            ParaNdis_DebugRegisterMiniport(pContext, TRUE);
            ParaNdis_IndicateConnect(pContext, FALSE, TRUE);
            ParaNdis5_StopSend(pContext, FALSE, NULL);
            ParaNdis5_StopReceive(pContext, FALSE, NULL);
            if (!pContext->ulMilliesToConnect)
            {
                ParaNdis_ReportLinkStatus(pContext, FALSE);
            }
            else
            {
                NdisSetTimer(&pContext->ConnectTimer, pContext->ulMilliesToConnect);
            }
        }
        else
        {
            ParaNdis_CleanupContext(pContext);
        }
    }

    if (status != NDIS_STATUS_SUCCESS && pContext)
    {
        NdisFreeMemory(pContext, 0, 0);
    }

    DEBUG_EXIT_STATUS(0, status);
    return status;
}


/*************************************************************
Callback of delayed receive pause procedure upon reset request
*************************************************************/
static void OnReceiveStoppedOnReset(VOID *p)
{
    PARANDIS_ADAPTER *pContext = (PARANDIS_ADAPTER *)p;
    DEBUG_ENTRY(0);
    NdisSetEvent(&pContext->ResetEvent);
}

/*************************************************************
Callback of delayed send pause procedure upon reset request
*************************************************************/
static void OnSendStoppedOnReset(VOID *p)
{
    PARANDIS_ADAPTER *pContext = (PARANDIS_ADAPTER *)p;
    DEBUG_ENTRY(0);
    NdisSetEvent(&pContext->ResetEvent);
}

VOID ParaNdis_Suspend(PARANDIS_ADAPTER *pContext)
{
    DEBUG_ENTRY(0);
    NdisResetEvent(&pContext->ResetEvent);
    if (NDIS_STATUS_PENDING != ParaNdis5_StopSend(pContext, TRUE, OnSendStoppedOnReset))
    {
        NdisSetEvent(&pContext->ResetEvent);
    }
    NdisWaitEvent(&pContext->ResetEvent, 0);
    NdisResetEvent(&pContext->ResetEvent);
    if (NDIS_STATUS_PENDING != ParaNdis5_StopReceive(pContext, TRUE, OnReceiveStoppedOnReset))
    {
        NdisSetEvent(&pContext->ResetEvent);
    }
    NdisWaitEvent(&pContext->ResetEvent, 0);
    NdisResetEvent(&pContext->ResetEvent);
    DEBUG_EXIT_STATUS(0, 0);
}

VOID ParaNdis_Resume(PARANDIS_ADAPTER *pContext)
{
    ParaNdis5_StopSend(pContext, FALSE, NULL);
    ParaNdis5_StopReceive(pContext, FALSE, NULL);
    DEBUG_EXIT_STATUS(0, 0);
}

static void OnResetWorkItem(NDIS_WORK_ITEM * pWorkItem, PVOID  Context)
{
    tGeneralWorkItem *pwi = (tGeneralWorkItem *)pWorkItem;
    PARANDIS_ADAPTER *pContext = pwi->pContext;
    DEBUG_ENTRY(0);

    pContext->bResetInProgress = TRUE;
    ParaNdis_IndicateConnect(pContext, FALSE, FALSE);
    ParaNdis_Suspend(pContext);
    ParaNdis_Resume(pContext);
    pContext->bResetInProgress = FALSE;
    ParaNdis_ReportLinkStatus(pContext, FALSE);

    NdisFreeMemory(pwi, 0, 0);
    ParaNdis_DebugHistory(pContext, hopSysReset, NULL, 0, NDIS_STATUS_SUCCESS, 0);
    NdisMResetComplete(pContext->MiniportHandle, NDIS_STATUS_SUCCESS, TRUE);
}


/*************************************************************
Required NDIS procedure
Called when some procedure (like OID handler) returns PENDING and
does not complete or when CheckForHang return TRUE
*************************************************************/
static NDIS_STATUS ParaNdis5_Reset(
    OUT PBOOLEAN AddressingReset,
    IN NDIS_HANDLE MiniportAdapterContext)
{
    NDIS_STATUS status;
    tGeneralWorkItem *pwi;
    PARANDIS_ADAPTER *pContext = (PARANDIS_ADAPTER *)MiniportAdapterContext;
    DEBUG_ENTRY(0);
    ParaNdis_DebugHistory(pContext, hopSysReset, NULL, 1, 0, 0);
    status = NDIS_STATUS_FAILURE;
    pwi = ParaNdis_AllocateMemory(pContext, sizeof(tGeneralWorkItem));
    if (pwi)
    {
        pwi->pContext = pContext;
        NdisInitializeWorkItem(&pwi->wi, OnResetWorkItem, pwi);
        if (NdisScheduleWorkItem(&pwi->wi) == NDIS_STATUS_SUCCESS)
        {
            status = NDIS_STATUS_PENDING;
        }
        else
        {
            NdisFreeMemory(pwi, 0, 0);
        }
    }
    if (status != NDIS_STATUS_PENDING)
    {
        ParaNdis_DebugHistory(pContext, hopSysReset, NULL, 0, status, 0);
    }
    return status;
}

/*************************************************************
Callback of delayed receive pause procedure
*************************************************************/
static VOID OnReceiveStopped(VOID *p)
{
    PARANDIS_ADAPTER *pContext = (PARANDIS_ADAPTER *)p;
    DEBUG_ENTRY(0);
    NdisSetEvent(&pContext->HaltEvent);
}

/*************************************************************
Callback of delayed send pause procedure
*************************************************************/
static VOID OnSendStopped(VOID *p)
{
    PARANDIS_ADAPTER *pContext = (PARANDIS_ADAPTER *)p;
    DEBUG_ENTRY(0);
    NdisSetEvent(&pContext->HaltEvent);
}

static void WaitHaltEvent(PARANDIS_ADAPTER *pContext, const char *Reason)
{
    UINT ms = 5000;
    if (!NdisWaitEvent(&pContext->HaltEvent, 1))
    {
        while (!NdisWaitEvent(&pContext->HaltEvent, ms))
        {
            DPrintf(0, ("[%s]", __FUNCTION__));
        }
    }
}

/*************************************************************
Required NDIS procedure
Stops TX and RX path and finished the function of adapter
*************************************************************/
static VOID ParaNdis5_Halt(
    IN NDIS_HANDLE MiniportAdapterContext)
{
    NDIS_STATUS status = NDIS_STATUS_SUCCESS;
    BOOLEAN bUnused;
    PARANDIS_ADAPTER *pContext = (PARANDIS_ADAPTER *)MiniportAdapterContext;
    DEBUG_ENTRY(0);

    ParaNdis_DebugHistory(pContext, hopHalt, NULL, 1, 0, 0);

    NdisCancelTimer(&pContext->ConnectTimer, &bUnused);
    NdisResetEvent(&pContext->HaltEvent);
    if (NDIS_STATUS_PENDING != ParaNdis5_StopSend(pContext, TRUE, OnSendStopped))
        NdisSetEvent(&pContext->HaltEvent);
    WaitHaltEvent(pContext, "Send");
    NdisResetEvent(&pContext->HaltEvent);
    if (NDIS_STATUS_PENDING != ParaNdis5_StopReceive(pContext, TRUE, OnReceiveStopped))
        NdisSetEvent(&pContext->HaltEvent);
    WaitHaltEvent(pContext, "Receive");
    ParaNdis_CleanupContext(pContext);
    NdisCancelTimer(&pContext->DPCPostProcessTimer, &bUnused);
    ParaNdis_DebugHistory(pContext, hopHalt, NULL, 0, 0, 0);
    ParaNdis_DebugRegisterMiniport(pContext, FALSE);
    NdisFreeMemory(pContext, 0, 0);
    DEBUG_EXIT_STATUS(0, status);
}


/*************************************************************
Called periodically (usually each 2 seconds)
*************************************************************/
static BOOLEAN ParaNdis5_CheckForHang(IN NDIS_HANDLE MiniportAdapterContext)
{
    PARANDIS_ADAPTER *pContext = (PARANDIS_ADAPTER *)MiniportAdapterContext;
    DEBUG_ENTRY(8);
    return ParaNdis_CheckForHang(pContext);
}

/*************************************************************
Required NDIS procedure
Responsible for hardware interrupt handling
*************************************************************/
static VOID ParaNdis5_MiniportISR(OUT PBOOLEAN InterruptRecognized,
                               OUT PBOOLEAN QueueMiniportHandleInterrupt,
                               IN NDIS_HANDLE  MiniportAdapterContext)
{
    PARANDIS_ADAPTER *pContext = (PARANDIS_ADAPTER *)MiniportAdapterContext;
    BOOLEAN b;
    *QueueMiniportHandleInterrupt = FALSE;
    b = ParaNdis_OnLegacyInterrupt(pContext, QueueMiniportHandleInterrupt);
    *InterruptRecognized = b;
    DEBUG_EXIT_STATUS(7, (ULONG)b);
}

/*************************************************************
Parameters:

Return value:

*************************************************************/
VOID ParaNdis5_PnPEventNotify(IN NDIS_HANDLE MiniportAdapterContext,
                                  IN NDIS_DEVICE_PNP_EVENT PnPEvent,
                                  IN PVOID InformationBuffer,
                                  IN ULONG InformationBufferLength)
{
    PARANDIS_ADAPTER *pContext = (PARANDIS_ADAPTER *)MiniportAdapterContext;
    ParaNdis_OnPnPEvent(pContext, PnPEvent, InformationBuffer, InformationBufferLength);
}

/*************************************************************
Driver's entry point
Parameters:
    as usual
Return value:
    SUCCESS or error code
*************************************************************/
NDIS_STATUS DriverEntry(PVOID DriverObject,PVOID RegistryPath)
{
    NDIS_STATUS status;
    NDIS_MINIPORT_CHARACTERISTICS chars;
    ParaNdis_DebugInitialize(DriverObject, RegistryPath);

    status = NDIS_STATUS_FAILURE;

    DEBUG_ENTRY(0);
    _LogOutString(0, __DATE__ " " __TIME__);

    NdisMInitializeWrapper(&DriverHandle,
                           DriverObject,
                           RegistryPath,
                           NULL
                           );

    if (DriverHandle)
    {
        NdisZeroMemory(&chars, sizeof(chars));
        //NDIS version of the miniport
        chars.MajorNdisVersion          = NDIS_MINIPORT_MAJOR_VERSION;
        chars.MinorNdisVersion          = NDIS_MINIPORT_MINOR_VERSION;
        //Init and destruction
        chars.InitializeHandler         = ParaNdis5_Initialize;
        chars.HaltHandler               = ParaNdis5_Halt;

        //Interrupt and DPC handling
        chars.HandleInterruptHandler    = ParaNdis5_HandleDPC;
        chars.ISRHandler                = ParaNdis5_MiniportISR;

        //Packet transfer - send path and notification on the send packet
        chars.SendPacketsHandler        = ParaNdis5_SendPackets;
        chars.ReturnPacketHandler       = ParaNdis5_ReturnPacket;

        //OID set\get
        chars.SetInformationHandler     = ParaNdis5_SetOID;
        chars.QueryInformationHandler   = ParaNdis5_QueryOID;

        //Reset
        chars.ResetHandler              = ParaNdis5_Reset;
        chars.CheckForHangHandler       = ParaNdis5_CheckForHang; //optional

        chars.CancelSendPacketsHandler  = ParaNdis5_CancelSendPackets;
        chars.PnPEventNotifyHandler     = ParaNdis5_PnPEventNotify;
        chars.AdapterShutdownHandler    = ParaNdis5_Shutdown;

        status = NdisMRegisterMiniport(
            DriverHandle,
            &chars,
            sizeof(chars));
    }

    if (status == NDIS_STATUS_SUCCESS)
    {
        NdisMRegisterUnloadHandler(DriverHandle, ParaVirtualNICUnload);
    }
    else if (DriverHandle)
    {
        DPrintf(0, ("NdisMRegisterMiniport failed"));
        NdisTerminateWrapper(DriverHandle, NULL);
    }
    else
    {
        DPrintf(0, ("NdisMInitializeWrapper failed"));
    }

    DEBUG_EXIT_STATUS(status ? 0 : 4, status);
    return status;
}
