/*========================================================================== * * Copyright (C) 1999 Microsoft Corporation. All Rights Reserved. * * File: Initialize.cpp * Content: This file contains code to both initialize and shutdown the * protocol, as well as to Add and Remove service providers * * History: * Date By Reason * ==== == ====== * 11/06/98 ejs Created * 07/01/2000 masonb Assumed Ownership * ****************************************************************************/ #include "dnproti.h" /* ** GLOBAL VARIABLES ** ** There are two kinds of global variables. Instance specific globals ** (not really global, i know) which are members of the ProtocolData structure, ** and true globals which are shared among all instances. The following ** definitions are true globals, such as FixedPools and Timers. */ LPFPOOL ChkPtPool = NULL; // Pool of CheckPoint data structure LPFPOOL EPDPool = NULL; // Pool of End Point descriptors LPFPOOL MSDPool = NULL; // Pool of Message Descriptors LPFPOOL FMDPool = NULL; // Pool of Frame Descriptors LPFPOOL RCDPool = NULL; // Pool of Receive Descriptors LPFPOOL BufPool = NULL; // Pool of buffers to store rcvd frames LPFPOOL MedBufPool = NULL; LPFPOOL BigBufPool = NULL; LONG g_lProtocolObjects = 0; DNCRITICAL_SECTION g_csProtocolGlobal; BOOL g_fTimerInited = FALSE; /* ** Pools Initialization ** ** This procedure should be called once at Dll load */ #undef DPF_MODNAME #define DPF_MODNAME "DNPPoolsInit" BOOL DNPPoolsInit() { DPFX(DPFPREP,DPF_CALLIN_LVL, "Enter"); if (DNInitializeCriticalSection(&g_csProtocolGlobal) == FALSE) { return FALSE; } if((ChkPtPool = FPM_Create(sizeof(CHKPT), NULL, NULL, NULL, NULL)) == NULL) { DNPPoolsDeinit(); return FALSE; } if((EPDPool = FPM_Create(sizeof(EPD), EPD_Allocate, EPD_Get, EPD_Release, EPD_Free)) == NULL) { DNPPoolsDeinit(); return FALSE; } if((MSDPool = FPM_Create(sizeof(MSD), MSD_Allocate, MSD_Get, MSD_Release, MSD_Free)) == NULL) { DNPPoolsDeinit(); return FALSE; } if((FMDPool = FPM_Create(sizeof(FMD), FMD_Allocate, FMD_Get, FMD_Release, FMD_Free)) == NULL) { DNPPoolsDeinit(); return FALSE; } if((RCDPool = FPM_Create(sizeof(RCD), RCD_Allocate, RCD_Get, RCD_Release, RCD_Free)) == NULL) { DNPPoolsDeinit(); return FALSE; } if((BufPool = FPM_Create(sizeof(BUF), Buf_Allocate, Buf_Get, NULL, NULL)) == NULL) { DNPPoolsDeinit(); return FALSE; } if((MedBufPool = FPM_Create(sizeof(MEDBUF), Buf_Allocate, Buf_GetMed, NULL, NULL)) == NULL) { DNPPoolsDeinit(); return FALSE; } if((BigBufPool = FPM_Create(sizeof(BIGBUF), Buf_Allocate, Buf_GetBig, NULL, NULL)) == NULL) { DNPPoolsDeinit(); return FALSE; } if (FAILED(TimerInit())) { DNPPoolsDeinit(); return FALSE; } g_fTimerInited = TRUE; return TRUE; } /* ** Pools Deinitialization ** ** This procedure should be called by DllMain at shutdown time */ #undef DPF_MODNAME #define DPF_MODNAME "DNPPoolsDeinit" void DNPPoolsDeinit() { DPFX(DPFPREP,DPF_CALLIN_LVL, "Enter"); if (g_fTimerInited) { TimerDeinit(); g_fTimerInited = FALSE; } DNDeleteCriticalSection(&g_csProtocolGlobal); if(ChkPtPool != NULL) { ChkPtPool->Fini(ChkPtPool); ChkPtPool = NULL; } if(EPDPool != NULL) { EPDPool->Fini(EPDPool); EPDPool = NULL; } if(MSDPool != NULL) { MSDPool->Fini(MSDPool); MSDPool = NULL; } if(FMDPool != NULL){ FMDPool->Fini(FMDPool); FMDPool = NULL; } if(RCDPool != NULL) { RCDPool->Fini(RCDPool); RCDPool = NULL; } if(BufPool != NULL) { BufPool->Fini(BufPool); BufPool = NULL; } if(MedBufPool != NULL) { MedBufPool->Fini(MedBufPool); MedBufPool = NULL; } if(BigBufPool != NULL) { BigBufPool->Fini(BigBufPool); BigBufPool = NULL; } } /* ** Protocol Initialize ** ** This procedure should be called by DirectPlay at startup time before ** any other calls in the protocol are made. */ #undef DPF_MODNAME #define DPF_MODNAME "DNPProtocolInitialize" HRESULT DNPProtocolInitialize(PVOID pCoreContext, PProtocolData pPData, PDN_PROTOCOL_INTERFACE_VTBL pVtbl) { DPFX(DPFPREP,DPF_CALLIN_LVL, "Parameters: pCoreContext[%p], pPData[%p], pVtbl[%p]", pCoreContext, pPData, pVtbl); // DPFX(DPFPREP,0, "Sizes: endpointdesc[%d], framedesc[%d], messagedesc[%d], protocoldata[%d], recvdesc[%d], spdesc[%d], _MyTimer[%d]", sizeof(endpointdesc), sizeof(framedesc), sizeof(messagedesc), sizeof(protocoldata), sizeof(recvdesc), sizeof(spdesc), sizeof(_MyTimer)); DNEnterCriticalSection(&g_csProtocolGlobal); g_lProtocolObjects++; if (g_lProtocolObjects == 1) { // We are the first, create everything DPFX(DPFPREP,5, "Initializing timers"); if (FAILED(InitTimerWorkaround())) { DPFX(DPFPREP,0, "Protocol timer package failed to initialize"); g_lProtocolObjects--; DNLeaveCriticalSection(&g_csProtocolGlobal); return DPNERR_GENERIC; } } DNLeaveCriticalSection(&g_csProtocolGlobal); pPData->ulProtocolFlags = 0; pPData->Parent = pCoreContext; pPData->pfVtbl = pVtbl; pPData->Sign = PD_SIGN; pPData->lSPActiveCount = 0; srand(GETTIMESTAMP()); pPData->dwNextSessID = rand() | (rand() << 16); // build a 32 bit value out of two 16 bit values pPData->tIdleThreshhold = DEFAULT_KEEPALIVE_INTERVAL; // 60 second keep-alive interval pPData->dwConnectTimeout = CONNECT_DEFAULT_TIMEOUT; pPData->dwConnectRetries = CONNECT_DEFAULT_RETRIES; #ifdef DEBUG pPData->ThreadsInReceive = 0; pPData->BuffersInReceive = 0; #endif pPData->ulProtocolFlags |= PFLAGS_PROTOCOL_INITIALIZED; return DPN_OK; } /* ** Protocol Shutdown ** ** This procedure should be called at termination time, and should be the ** last call made to the protocol. ** ** All SPs should have been removed prior to this call which in turn means ** that we should not have any sends pending in a lower layer. */ #undef DPF_MODNAME #define DPF_MODNAME "DNPProtocolShutdown" HRESULT DNPProtocolShutdown(PProtocolData pPData) { DPFX(DPFPREP,DPF_CALLIN_LVL, "Parameters: pPData[%p]", pPData); DNEnterCriticalSection(&g_csProtocolGlobal); g_lProtocolObjects--; if (g_lProtocolObjects == 0) { // We are the last, destroy everything DPFX(DPFPREP,5, "Destroying timers"); FiniTimerWorkaround(); } DNLeaveCriticalSection(&g_csProtocolGlobal); if(pPData->lSPActiveCount != 0) { DPFX(DPFPREP,0, "Returning DPNERR_INVALIDCOMMAND - There are still active SPs, DNPRemoveSP wasn't called"); return DPNERR_INVALIDCOMMAND; // Must remove Service Providers first } #ifdef DEBUG if (pPData->BuffersInReceive != 0) { DPFX(DPFPREP,0, "*** %d receive buffers were leaked", pPData->BuffersInReceive); } #endif pPData->ulProtocolFlags = 0; return DPN_OK; } /* ** Add Service Provider ** ** This procedure is called by Direct Play to bind us to a service provider. ** We can bind up to 256 service providers at one time, although I would not ever ** expect to do so. This procedure will fail if Protocol Initialize has not ** been called. ** ** ** We check the size of the SP table to make sure we have a slot free. If table ** is full we double the table size until we reach maximum size. If table cannot grow ** then we fail the AddServiceProvider call. */ extern IDP8SPCallbackVtbl DNPLowerEdgeVtbl; #undef DPF_MODNAME #define DPF_MODNAME "DNPAddServiceProvider" HRESULT DNPAddServiceProvider(PProtocolData pPData, IDP8ServiceProvider *pISP, HANDLE *pContext) { PSPD pSPD=0; SPINITIALIZEDATA SPInitData; SPGETCAPSDATA SPCapsData; HRESULT hr; *pContext = NULL; DPFX(DPFPREP,DPF_CALLIN_LVL, "Parameters: pPData[%p], pISP[%p], pContext[%p]", pPData, pISP, pContext); if(pPData->ulProtocolFlags & PFLAGS_PROTOCOL_INITIALIZED) { if ((pSPD = (PSPD)DNMalloc(sizeof(SPD))) == NULL) { DPFX(DPFPREP,0, "Returning DPNERR_OUTOFMEMORY - couldn't allocate SP Descriptor"); return DPNERR_OUTOFMEMORY; } // MAKE THE INITIALIZE CALL TO THE Service Provider... give him our Object memset(pSPD, 0, sizeof(SPD)); // init to zero pSPD->LowerEdgeVtable = &DNPLowerEdgeVtbl; // Put Vtbl into the interface Object pSPD->Sign = SPD_SIGN; SPInitData.pIDP = (IDP8SPCallback *) pSPD; SPInitData.dwFlags = 0; if (DNInitializeCriticalSection(&pSPD->SPLock) == FALSE) { DPFX(DPFPREP,0, "Returning DPNERR_OUTOFMEMORY - couldn't initialize SP CS, pSPD[%p]", pSPD); DNFree(pSPD); return DPNERR_OUTOFMEMORY; } DebugSetCriticalSectionRecursionCount(&pSPD->SPLock, 0); DPFX(DPFPREP,DPF_CALLOUT_LVL, "Calling SP->Initialize, pSPD[%p]", pSPD); if((hr = IDP8ServiceProvider_Initialize(pISP, &SPInitData)) != DPN_OK) { DPFX(DPFPREP,0, "Returning hr=%x - SP->Initialize failed, pSPD[%p]", hr, pSPD); DNDeleteCriticalSection(&pSPD->SPLock); DNFree(pSPD); return hr; } pSPD->blSendQueue.Initialize(); pSPD->blPendingQueue.Initialize(); pSPD->blEPDActiveList.Initialize(); #ifdef DEBUG pSPD->blMessageList.Initialize(); #endif // MAKE THE SP GET CAPS CALL TO FIND FRAMESIZE AND LINKSPEED SPCapsData.dwSize = sizeof(SPCapsData); SPCapsData.hEndpoint = INVALID_HANDLE_VALUE; DPFX(DPFPREP,DPF_CALLOUT_LVL, "Calling SP->GetCaps, pSPD[%p]", pSPD); if((hr = IDP8ServiceProvider_GetCaps(pISP, &SPCapsData)) != DPN_OK) { DPFX(DPFPREP,DPF_CALLOUT_LVL, "SP->GetCaps failed - hr[%x], Calling SP->Close, pSPD[%p]", hr, pSPD); IDP8ServiceProvider_Close(pISP); DNDeleteCriticalSection(&pSPD->SPLock); DPFX(DPFPREP,0, "Returning hr=%x - SP->GetCaps failed, pSPD[%p]", hr, pSPD); DNFree(pSPD); return hr; } pSPD->uiLinkSpeed = SPCapsData.dwLocalLinkSpeed; pSPD->uiFrameLength = SPCapsData.dwUserFrameSize; pSPD->uiUserFrameLength = pSPD->uiFrameLength - DNP_MAX_HEADER_SIZE; // Place new SP in table DPFX(DPFPREP,DPF_CALLOUT_LVL, "Calling SP->AddRef, pSPD[%p]", pSPD); IDP8ServiceProvider_AddRef(pISP); pSPD->IISPIntf = pISP; pSPD->pPData = pPData; InterlockedIncrement(&pPData->lSPActiveCount); } else { DPFX(DPFPREP,0, "Returning DPNERR_UNINITIALIZED - DNPProtocolInitialize has not been called"); return DPNERR_UNINITIALIZED; } *pContext = pSPD; return DPN_OK; } /* ** Remove Service Provider ** ** It is higher layer's responsibility to make sure that there are no pending commands ** when this function is called, although we can do a certain amount of cleanup ourselves. ** For the moment will we ASSERT that everything is in fact finished up. ** ** SPTable stuff... Since we use the table slot as the SP identiier, we must not compact ** the SP table upon removal. Therefore, we must have a way of validating the SP index, and ** we may not want to re-use table slots after an SP is removed. Since we have virtually ** unlimited space in the table, and SPs are generally not intended to be transitory, its ** probably safe to invalidate the old table slot and just keep increasing the IDs. */ #undef DPF_MODNAME #define DPF_MODNAME "DNPRemoveServiceProvider" HRESULT DNPRemoveServiceProvider(PProtocolData pPData, HANDLE hSPHandle) { PSPD pSPD = NULL; PEPD pEPD; PMSD pMSD; PFMD pFMD; pSPD = (PSPD) hSPHandle; DPFX(DPFPREP,DPF_CALLIN_LVL, "Parameters: pPData[%p], hSPHandle[%x]", pPData, hSPHandle); if(pSPD == NULL) { DPFX(DPFPREP,0, "Returning DPNERR_INVALIDOBJECT - SP Handle is NULL"); return DPNERR_INVALIDOBJECT; } // There are several steps to shutdown: // 1. All Core initiated commands must be cancelled prior to this function being called. // We will assert in debug that the Core has done this. // 2. All endpoints must be terminated by the Core prior to this function being called. // We will assert in debug that the Core has done this. // Now there are things on the SPD->SendQueue and SPD->PendingQueue that are not owned // by any Command or Endpoint, and there may also be a SendThread Timer running held // on SPD->SendHandle. No one else can clean these up, so these are our responsibility // to clean up here. Items on the queues will be holding references to EPDs, so the // EPDs will not be able to go away until we do this. // 3. Cancel SPD->SendHandle Send Timer. This prevents items on the SendQueue from // being submitted to the SP and moved to the PendingQueue. // 4. Empty the SendQueue. // 5. If we fail to cancel the SendHandle Send Timer, wait for it to run and figure out // that we are going away. We do this after emptying the SendQueue for simplicity // since the RunSendThread code checks for an empty SendQueue to know if it has work // to do. // 6. Wait for all messages to drain from the PendingQueue as the SP completes them. // 7. Wait for any active EPDs to go away. // 8. Call SP->Close only after all of the above so that we can ensure that we will make // no calls to the SP after Close. Lock(&pSPD->SPLock); pSPD->ulSPFlags |= SPFLAGS_TERMINATING; // Nothing new gets in... #ifdef DEBUG // Check for uncancelled commands, SPLock held CBilink* pLink = pSPD->blMessageList.GetNext(); while (pLink != &pSPD->blMessageList) { pMSD = CONTAINING_RECORD(pLink, MSD, blSPLinkage); ASSERT_MSD(pMSD); ASSERT(pMSD->ulMsgFlags1 & MFLAGS_ONE_ON_GLOBAL_LIST); DPFX(DPFPREP,0, "There are un-cancelled commands remaining on the Command List, Core didn't clean up properly - pMSD[%p], Context[%x]", pMSD, pMSD->Context); ASSERT(0); // This is fatal, we can't make the guarantees we need to below under these conditions. pLink = pLink->GetNext(); } // Check for EPDs that have not been terminated, SPLock still held pLink = pSPD->blEPDActiveList.GetNext(); while (pLink != &pSPD->blEPDActiveList) { pEPD = CONTAINING_RECORD(pLink, EPD, blActiveLinkage); ASSERT_EPD(pEPD); if (!(pEPD->ulEPFlags & EPFLAGS_STATE_TERMINATING)) { DPFX(DPFPREP,0, "There are non-terminated endpoints remaining on the Endpoint List, Core didn't clean up properly - pEPD[%p], Context[%x]", pEPD, pEPD->Context); ASSERT(0); // This is fatal, we can't make the guarantees we need to below under these conditions. } pLink = pLink->GetNext(); } #endif // See if we still have a Send Event pending, SPLock still held if(pSPD->SendHandle != NULL) { DPFX(DPFPREP,1, "Shutting down with send event still pending, cancelling, pSPD=[%p]", pSPD); if(CancelMyTimer(pSPD->SendHandle, pSPD->SendHandleUnique) == DPN_OK) { pSPD->SendHandle = NULL; pSPD->ulSPFlags &= ~(SPFLAGS_SEND_THREAD_SCHEDULED); } else { DPFX(DPFPREP,1, "Failed to cancel send event", pSPD); } } // Clean off the Send Queue, SPLock still held while(!pSPD->blSendQueue.IsEmpty()) { pFMD = CONTAINING_RECORD(pSPD->blSendQueue.GetNext(), FMD, blQLinkage); ASSERT_FMD(pFMD); ASSERT_EPD(pFMD->pEPD); DPFX(DPFPREP,1, "Cleaning FMD off of SendQueue pSPD[%p], pFMD[%p], pEPD[%p]", pSPD, pFMD, pFMD->pEPD); pFMD->blQLinkage.RemoveFromList(); // RELEASE_EPD will need to have the EPD lock, so we cannot hold the SPLock while calling it. Unlock(&pSPD->SPLock); Lock(&pFMD->pEPD->EPLock); RELEASE_EPD(pFMD->pEPD, "UNLOCK (Releasing Leftover CMD FMD)"); // releases EPLock RELEASE_FMD(pFMD, "SP Submit"); Lock(&pSPD->SPLock); } // In case we failed to cancel the SendHandle Timer above, wait for the send thread to run and figure // out that we are going away. We want to be outside the SPLock while doing this. while(pSPD->ulSPFlags & SPFLAGS_SEND_THREAD_SCHEDULED) { Unlock(&pSPD->SPLock); Sleep(0); // Give up our time slice Lock(&pSPD->SPLock); } // Clean off the Pending Queue, SPLock still held while (!pSPD->blPendingQueue.IsEmpty()) { Unlock(&pSPD->SPLock); Sleep(0); // Give up our time slice Lock(&pSPD->SPLock); } // By now we are only waiting for the SP to do any final calls to CommandComplete that are needed to take // our EPD ref count down to nothing. We will wait while the SP does this. while(!(pSPD->blEPDActiveList.IsEmpty())) { Unlock(&pSPD->SPLock); Sleep(0); // Give up our time slice Lock(&pSPD->SPLock); } // By this time everything pending had better be gone! ASSERT(pSPD->blEPDActiveList.IsEmpty()); // Should not be any Endpoints left ASSERT(pSPD->blSendQueue.IsEmpty()); // Should not be any frames on sendQ. ASSERT(pSPD->blPendingQueue.IsEmpty()); // Should not be any frame in SP either // Leave SPLock for the last time Unlock(&pSPD->SPLock); // Now that all frames are cleared out of SP, there should be no more End Points waiting around to close. // We are clear to tell the SP to go away. DPFX(DPFPREP,DPF_CALLOUT_LVL, "Calling SP->Close, pSPD[%p]", pSPD); IDP8ServiceProvider_Close(pSPD->IISPIntf); DPFX(DPFPREP,DPF_CALLOUT_LVL, "Calling SP->Release, pSPD[%p]", pSPD); IDP8ServiceProvider_Release(pSPD->IISPIntf); // Clean up the SPD object DNDeleteCriticalSection(&pSPD->SPLock); DNFree(pSPD); // Remove the reference of this SP from the main Protocol object ASSERT(pPData->lSPActiveCount > 0); InterlockedDecrement(&pPData->lSPActiveCount); return DPN_OK; }