windows-nt/Source/XPSP1/NT/multimedia/directx/dplay/dnet/protocol/initialize.cpp
2020-09-26 16:20:57 +08:00

571 lines
17 KiB
C++

/*==========================================================================
*
* 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;
}