/*++										

Copyright (c) 1997  Microsoft Corporation

Module Name:

	atkpnp.c

Abstract:

	This module contains the support code for handling PnP events

Author:

	Shirish Koti

Revision History:
	16 Jun 1997		Initial Version

--*/


#include <atalk.h>
#pragma hdrstop
#define	FILENUM	ATKPNP

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, AtalkPnPHandler)
#pragma alloc_text(PAGE, AtalkPnPReconfigure)
#pragma alloc_text(PAGE, AtalkPnPEnableAdapter)
#endif

NDIS_STATUS
AtalkPnPHandler(
    IN  NDIS_HANDLE    NdisBindCtx,
    IN  PNET_PNP_EVENT pPnPEvent
)
{

    NDIS_STATUS     Status=STATUS_SUCCESS;

    PAGED_CODE();

    ASSERT(pPnPEvent);

    ASSERT(KeGetCurrentIrql() == 0);

    switch (pPnPEvent->NetEvent)
    {
        case NetEventReconfigure:

	            DBGPRINT(DBG_COMP_ACTION, DBG_LEVEL_INFO,
		            ("AtalkPnPHandler: NetEventReconfigure event\n"));

                Status = AtalkPnPReconfigure(NdisBindCtx,pPnPEvent);

                break;

        case NetEventCancelRemoveDevice:

	            DBGPRINT(DBG_COMP_ACTION, DBG_LEVEL_ERR,
		            ("AtalkPnPHandler: NetEventCancelRemoveDevice event\n"));
                break;

        case NetEventQueryRemoveDevice:

	            DBGPRINT(DBG_COMP_ACTION, DBG_LEVEL_ERR,
		            ("AtalkPnPHandler: NetEventQueryRemoveDevice event\n"));
                break;

        case NetEventQueryPower:

	            DBGPRINT(DBG_COMP_ACTION, DBG_LEVEL_ERR,
		            ("AtalkPnPHandler: NetEventQueryPower event\n"));
                break;

        case NetEventSetPower:

	            DBGPRINT(DBG_COMP_ACTION, DBG_LEVEL_ERR,
		            ("AtalkPnPHandler: NetEventSetPower event\n"));
                break;

        case NetEventBindsComplete:
	            DBGPRINT(DBG_COMP_ACTION, DBG_LEVEL_ERR,
		            ("AtalkPnPHandler: NetEventBindsComplete event\n"));
                break;

        case NetEventBindList:
	            DBGPRINT(DBG_COMP_ACTION, DBG_LEVEL_ERR,
		            ("AtalkPnPHandler: NetEventBindList event\n"));
                break;

        default:
	            DBGPRINT(DBG_COMP_ACTION, DBG_LEVEL_ERR,
		            ("AtalkPnPHandler: what is this event?, verify if it is valid/new = %ld\n", pPnPEvent->NetEvent));

		break;
    }

    ASSERT(Status == STATUS_SUCCESS);

    return(STATUS_SUCCESS);
}


NDIS_STATUS
AtalkPnPReconfigure(
    IN  NDIS_HANDLE    NdisBindCtx,
    IN  PNET_PNP_EVENT pPnPEvent
)
{

    NTSTATUS            Status=STATUS_SUCCESS;
    NTSTATUS            LocStatus=STATUS_SUCCESS;
    PPORT_DESCRIPTOR    pPortDesc;
    PPORT_DESCRIPTOR    pPrevPortDesc;
    PPORT_DESCRIPTOR    pNextPortDesc;
    PPORT_DESCRIPTOR    pFirstPortDesc;
    PPORT_DESCRIPTOR    pWalkerPortDesc;
    PATALK_PNP_EVENT    pPnpBuf;
    BOOLEAN             fWeFoundOut;


    pPortDesc = (PPORT_DESCRIPTOR)NdisBindCtx;

    pPnpBuf = (PATALK_PNP_EVENT)(pPnPEvent->Buffer);

    //
    // if it's a global configuration message, just ignore it because we will
    // be getting (or have already got) specific messages
    //
    if (pPnpBuf == NULL)
    {
		DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
		    ("AtalkPnPReconfigure: ignoring global config message\n"));
        return(STATUS_SUCCESS);
    }

    if ((!pPortDesc) &&
        (pPnpBuf->PnpMessage != AT_PNP_SWITCH_ROUTING) &&
        (pPnpBuf->PnpMessage != AT_PNP_SWITCH_DEFAULT_ADAPTER))
    {
		DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
		    ("AtalkPnPReconfigure: ignoring NULL context (pnp msg = %d)\n",
            pPnpBuf->PnpMessage));
        return(STATUS_SUCCESS);
    }

    if (AtalkBindnUnloadStates & ATALK_UNLOADING)
    {
		DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
		    ("AtalkPnPReconfigure: stack is shutting down, ignoring pnp\n"));
        return(STATUS_SUCCESS);
    }

    AtalkBindnUnloadStates |= ATALK_PNP_IN_PROGRESS;

	DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR, ("\n\nProcessing PnP Event....\n\n"));

    AtalkLockInitIfNecessary();

    ASSERT(pPnpBuf != NULL);

    switch (pPnpBuf->PnpMessage)
    {
        //
        // user just checked (or unchecked) the router checkbox!  If we are
        // currently not routing, we must start routing.  If we are currently
        // routing, we must stop routing.  "Disable" all the adapters, go read
        // the global config info and "Enable" all the adapters back.
        //
        case AT_PNP_SWITCH_ROUTING:

			DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
			    ("AtalkPnPReconfigure: AT_PNP_SWITCH_ROUTING. Currently, routing is %s\n"
                ,(AtalkRouter)? "ON" : "OFF" ));

            pPortDesc = pFirstPortDesc = AtalkPortList;
            pPrevPortDesc = pPortDesc;

            if (!pPortDesc)
            {
			    DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
			        ("AtalkPnPReconfigure: no adapter configured! no action taken\n"));
                break;
            }

            //
            // if we are currently running the router, first stop the global
            // rtmp and zip timers
            //
            if (AtalkRouter)
            {
                if (AtalkTimerCancelEvent(&atalkRtmpVTimer, NULL))
                {
			        DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
			            ("AtalkPnPReconfigure: cancelled atalkRtmpValidityTimer\n"));
                }
                else
                {
			        DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
			            ("AtalkPnPReconfigure: couldn't cancel atalkRtmpValidityTimer\n"));
                }

                if (AtalkTimerCancelEvent(&atalkZipQTimer, NULL))
                {
			        DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
			            ("AtalkPnPReconfigure: cancelled atalkZipQueryTimer\n"));
                }
                else
                {
			        DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
			            ("AtalkPnPReconfigure: couldn't cancel atalkZipQueryTimer\n"));
                }
            }

            atalkRtmpVdtTmrRunning  = FALSE;

            atalkZipQryTmrRunning   = FALSE;

            //
            // now, disable all the ports in the list one by one.  This actually
            // removes the adapter from the list as well.  Link all these adapters
            // together so we can enable all of them.
            // (NDIS guaranteed that no ndis event (pnp, unbind etc.) can happen
            // when one is in progress, so we don't need lock here)
            //
            while (pPortDesc != NULL)
            {

                DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
                    ("AtalkPnPReconfigure: disabling pPortDesc %lx\n",pPortDesc));

                Status = AtalkPnPDisableAdapter(pPortDesc);

                pPortDesc = AtalkPortList;
                pPrevPortDesc->pd_Next = pPortDesc;
                pPrevPortDesc = pPortDesc;
            }

            // unlock the pages that we locked when router was first started
            if (AtalkRouter)
            {
                AtalkUnlockRouterIfNecessary();
            }

            if (AtalkDefaultPortName.Buffer)
            {
                AtalkFreeMemory(AtalkDefaultPortName.Buffer);
                AtalkDefaultPortName.Buffer = NULL;
            }

            if (AtalkDesiredZone)
            {
                ASSERT(AtalkDesiredZone->zn_RefCount >= 1);
                AtalkZoneDereference(AtalkDesiredZone);
                AtalkDesiredZone = NULL;
            }

            // get rid of routing table, if one exists
            AtalkRtmpInit(FALSE);

            // go read all the parms again: registry must have changed
            LocStatus = atalkInitGlobal();

            ASSERT(NT_SUCCESS(LocStatus));

            // now, enable all the adapters back!
            pPortDesc = pFirstPortDesc;

            while (pPortDesc != NULL)
            {
                pNextPortDesc = pPortDesc->pd_Next;
                pPortDesc->pd_Next = NULL;

                DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
                    ("AtalkPnPReconfigure: enabling pPortDesc %lx\n",pPortDesc));

                Status = AtalkPnPEnableAdapter(pPortDesc);

                pPortDesc = pNextPortDesc;
            }

            break;

        //
        // user has changed the default adapter.  First, "disable" our
        // current default adapter and the wannabe default adapter.  Then,
        // "enable" both the adapters, and that should take care of everything!
        //
        case AT_PNP_SWITCH_DEFAULT_ADAPTER:

			DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
			    ("AtalkPnPReconfigure: AT_PNP_SWITCH_DEFAULT_ADAPTER (old=(%lx) new=(%lx)\n",
                AtalkDefaultPort,pPortDesc));

            pPrevPortDesc = AtalkDefaultPort;

            // check if default adapter exists: it's possible that at this moment there isn't one
            if (pPrevPortDesc)
            {
                Status = AtalkPnPDisableAdapter(pPrevPortDesc);
            }

            // release the default adapter name buffer, and desired zone buffer
            if (AtalkDefaultPortName.Buffer)
            {
                AtalkFreeMemory(AtalkDefaultPortName.Buffer);
                AtalkDefaultPortName.Buffer = NULL;
            }

            if (AtalkDesiredZone)
            {
                ASSERT(AtalkDesiredZone->zn_RefCount >= 1);
                AtalkZoneDereference(AtalkDesiredZone);
                AtalkDesiredZone = NULL;
            }

            // go read all the parms again: registry must have changed
            LocStatus = atalkInitGlobal();

            ASSERT(NT_SUCCESS(LocStatus));

            fWeFoundOut = FALSE;

            ASSERT(AtalkDefaultPortName.Buffer != NULL);

            // if we know who the new default adapter is going to be, disable him now
            if (pPortDesc != NULL)
            {
                Status = AtalkPnPDisableAdapter(pPortDesc);
            }

            //
            // UI doesn't know who the default adapter is, so let's find out
            // AtalkDefaultPortName.Buffer can not be null, but let's not bugcheck if
            // there is some problem in how UI does things.
            //
            else if (AtalkDefaultPortName.Buffer != NULL)
            {
                //
                // note that we aren't holding AtalkPortLock here.  The only way
                // the list can change is if an adapter binds or unbinds.  Since ndis
                // guarantees that all bind/unbind/pnp operations are serialized, and
                // since ndis has already called us here, the list can't change.
                //
                pPortDesc = AtalkPortList;

                while (pPortDesc != NULL)
                {
	                if (RtlEqualUnicodeString(&pPortDesc->pd_AdapterName,
		    		            	          &AtalkDefaultPortName,
			    		                      TRUE))
	                {
                        fWeFoundOut = TRUE;
	                    break;
	                }

                    pPortDesc = pPortDesc->pd_Next;
                }

                if (pPortDesc == NULL)
                {
			        DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
			            ("AtalkPnPReconfigure: still no default port????\n"));
                }
            }

            //
            // if there default adapter existed before this, reenable it (to be
            // a non-default adapter)
            //
            if (pPrevPortDesc)
            {
                Status = AtalkPnPEnableAdapter(pPrevPortDesc);
            }

            //
            // if we were told who the default adapter is, or if we found out
            // ourselves and one of the existing adapters is the default adatper,
            // disable it and reenable
            //
            if (pPortDesc)
            {
                // disable this guy if we found him out
                if (fWeFoundOut)
                {
                    Status = AtalkPnPDisableAdapter(pPortDesc);
                }

                // reenable the new adapter so that it is now the default adatper
                Status = AtalkPnPEnableAdapter(pPortDesc);

			    DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
			        ("AtalkPnPReconfigure: %lx is the new default adapter\n",pPortDesc));
                ASSERT(AtalkDefaultPort == pPortDesc);
            }
            else
            {
			    DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
			        ("AtalkPnPReconfigure: no default adapter configured!\n"));
            }

            break;

        //
        // user has changed some parameter on the adapter (e.g. the desired zone,
        // or some seeding info etc.).  Just "disable" and then "enable" this
        // adapter, and everything should just work!
        //
        case AT_PNP_RECONFIGURE_PARMS:

			DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
			    ("AtalkPnPReconfigure: AT_PNP_RECONFIGURE_PARMS on pPortDesc %lx\n",pPortDesc));

            Status = AtalkPnPDisableAdapter(pPortDesc);

            // release the default adapter name buffer, and desired zone buffer
            if (AtalkDefaultPortName.Buffer)
            {
                AtalkFreeMemory(AtalkDefaultPortName.Buffer);
                AtalkDefaultPortName.Buffer = NULL;
            }

            if (AtalkDesiredZone)
            {
                ASSERT(AtalkDesiredZone->zn_RefCount >= 1);
                AtalkZoneDereference(AtalkDesiredZone);
                AtalkDesiredZone = NULL;
            }

            // go read all the parms again: registry must have changed
            LocStatus = atalkInitGlobal();

            ASSERT(NT_SUCCESS(LocStatus));

            Status = AtalkPnPEnableAdapter(pPortDesc);

            break;


        default:

			DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
			    ("AtalkPnPReconfigure: and what msg is this (%ld) ??\n",pPnpBuf->PnpMessage));

            ASSERT(0);

            break;
    }


    AtalkUnlockInitIfNecessary();

    ASSERT(Status == STATUS_SUCCESS);

    AtalkBindnUnloadStates &= ~ATALK_PNP_IN_PROGRESS;

	DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
	    ("\n\n.... completed processing PnP Event\n\n"));

    return(STATUS_SUCCESS);
}



NTSTATUS
AtalkPnPDisableAdapter(
	IN	PPORT_DESCRIPTOR	pPortDesc
)
{

    NTSTATUS        Status;
    KIRQL           OldIrql;
    PLIST_ENTRY     pList;
    PARAPCONN       pArapConn;
    PATCPCONN       pAtcpConn;
    BOOLEAN         fDllDeref;
    BOOLEAN         fLineDownDeref;


    if (!pPortDesc)
    {
	    DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
	        ("AtalkPnPDisableAdapter: pPortDesc is NULL!!!\n"));

        return(STATUS_SUCCESS);
    }

    ASSERT(VALID_PORT(pPortDesc));

	DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
	    ("AtalkPnPDisableAdapter: entered with %lx\n",pPortDesc));

    //
    // we are going to "disable" this port due to PnP: note that fact!
    //
	ACQUIRE_SPIN_LOCK(&pPortDesc->pd_Lock, &OldIrql);
	pPortDesc->pd_Flags |= PD_PNP_RECONFIGURE;
	RELEASE_SPIN_LOCK(&pPortDesc->pd_Lock, OldIrql);

    // First and foremost: tell guys above so they can cleanup
    if (pPortDesc->pd_Flags & PD_DEF_PORT)
    {
        ASSERT(pPortDesc == AtalkDefaultPort);

        if (TdiAddressChangeRegHandle)
        {
            TdiDeregisterNetAddress(TdiAddressChangeRegHandle);
            TdiAddressChangeRegHandle = NULL;

            DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
                ("AtalkPnPDisableAdapter: TdiDeregisterNetAddress on %Z done\n",
                &pPortDesc->pd_AdapterName));

        }

        // this will tell AFP
        if (TdiRegistrationHandle)
        {
	        DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
			    ("AtalkPnPDisableAdapter: telling AFP about PnP\n"));

            TdiDeregisterDeviceObject(TdiRegistrationHandle);
            TdiRegistrationHandle = NULL;
        }

        // this will take care of informing ARAP and PPP engine above
        AtalkPnPInformRas(FALSE);
    }

    //
    // if this is RAS port or the Default port, kill all the ARAP and PPP
    // connections if any are left.
    // Since we've marked that PnpReconfigure is in progress, no more
    // new connections will be allowed
    //
    if ((pPortDesc == RasPortDesc) ||
        ((pPortDesc->pd_Flags & PD_DEF_PORT) && (RasPortDesc != NULL)))
    {
        ACQUIRE_SPIN_LOCK(&RasPortDesc->pd_Lock, &OldIrql);

        pList = RasPortDesc->pd_ArapConnHead.Flink;

        // first, the ARAP guys
        while (pList != &RasPortDesc->pd_ArapConnHead)
        {
            pArapConn = CONTAINING_RECORD(pList, ARAPCONN, Linkage);

            ASSERT(pArapConn->Signature == ARAPCONN_SIGNATURE);

            // if this connection is already disconnected, skip it
            ACQUIRE_SPIN_LOCK_DPC(&pArapConn->SpinLock);
            if (pArapConn->State == MNP_DISCONNECTED)
            {
                pList = pArapConn->Linkage.Flink;
                RELEASE_SPIN_LOCK_DPC(&pArapConn->SpinLock);
                continue;
            }

            RELEASE_SPIN_LOCK_DPC(&pArapConn->SpinLock);
            RELEASE_SPIN_LOCK(&RasPortDesc->pd_Lock, OldIrql);

	        DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
	            ("AtalkPnPDisableAdapter: killing ARAP connection %lx\n",pArapConn));

            ArapCleanup(pArapConn);

            ACQUIRE_SPIN_LOCK(&RasPortDesc->pd_Lock, &OldIrql);

            pList = RasPortDesc->pd_ArapConnHead.Flink;
        }

        // and now, the PPP guys

        // if there are any ppp guys, remove them from this list and dereference
        // them.  In most cases, they will get freed right away.  If someone had
        // a refcount, it will get freed when that refcount goes away
        while (!(IsListEmpty(&RasPortDesc->pd_PPPConnHead)))
        {
            pList = RasPortDesc->pd_PPPConnHead.Flink;
            pAtcpConn = CONTAINING_RECORD(pList, ATCPCONN, Linkage);

            ASSERT(pAtcpConn->Signature == ATCPCONN_SIGNATURE);

            ACQUIRE_SPIN_LOCK_DPC(&pAtcpConn->SpinLock);

            RemoveEntryList(&pAtcpConn->Linkage);
            InitializeListHead(&pAtcpConn->Linkage);

            fDllDeref = (pAtcpConn->Flags & ATCP_DLL_SETUP_DONE)? TRUE : FALSE;
            fLineDownDeref = (pAtcpConn->Flags & ATCP_LINE_UP_DONE)? TRUE : FALSE;

            pAtcpConn->Flags &= ~(ATCP_DLL_SETUP_DONE|ATCP_LINE_UP_DONE);

            RELEASE_SPIN_LOCK_DPC(&pAtcpConn->SpinLock);
            RELEASE_SPIN_LOCK(&RasPortDesc->pd_Lock, OldIrql);

	        DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
	            ("AtalkPnPDisableAdapter: deref'ing PPP conn %lx (%d+%d times)\n",
                pAtcpConn,fDllDeref,fLineDownDeref));

            // remove the DLL refcount
            if (fDllDeref)
            {
                DerefPPPConn(pAtcpConn);
            }

            // remove the NDISWAN refcount
            if (fLineDownDeref)
            {
                DerefPPPConn(pAtcpConn);
            }

            ACQUIRE_SPIN_LOCK(&RasPortDesc->pd_Lock, &OldIrql);
        }

        RELEASE_SPIN_LOCK(&RasPortDesc->pd_Lock, OldIrql);
    }

    //
    // "Disable" the adapter (basically we do everything except close the
    // adapter with ndis and freeing up the pPortDesc memory)
    //
	Status = AtalkDeinitAdapter(pPortDesc);

    if (!NT_SUCCESS(Status))
    {
	    DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
	        ("AtalkPnPDisableAdapter: AtalkDeinitAdapter failed %lx\n",Status));
        ASSERT(0);
    }
    return(Status);
}


NTSTATUS
AtalkPnPEnableAdapter(
	IN	PPORT_DESCRIPTOR	pPortDesc
)
{

    NTSTATUS        Status;


    if (!pPortDesc)
    {
	    DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
	        ("AtalkPnPDisableAdapter: pPortDesc is NULL!!!\n"));

        return(STATUS_SUCCESS);
    }


	DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
	    ("AtalkPnPEnableAdapter: entered with %lx\n",pPortDesc));

    //
    // "Enable" the adapter (we do everything except that we don't
    // allocate memory for pPortDesc - since we didn't free it, and we don't
    // open the adapter with ndis - since we didn't close it).
    //

	Status = AtalkInitAdapter(NULL, pPortDesc);

    // we are done with the PnPReconfigure evnet: reset that bit
    AtalkPortSetResetFlag(pPortDesc, TRUE, PD_PNP_RECONFIGURE);

    // tell ARAP everything is ok
    if (pPortDesc->pd_Flags & (PD_DEF_PORT | PD_RAS_PORT))
    {
        ASSERT((pPortDesc == AtalkDefaultPort) || (pPortDesc == RasPortDesc));

        // this will take care of informing ARAP and PPP engine above
        AtalkPnPInformRas(TRUE);
    }


    if (!NT_SUCCESS(Status))
    {
	    DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
	        ("AtalkPnPEnableAdapter: AtalkInitAdapter failed %lx\n",Status));
        ASSERT(0);
    }

    DBGPRINT(DBG_COMP_INIT, DBG_LEVEL_ERR,
        ("AtalkPnPEnableAdapter: completed PnP on %lx (flag %lx)\n",
        pPortDesc,pPortDesc->pd_Flags));

    return(Status);
}