|
|
// Copyright (c) 1997, Microsoft Corporation, all rights reserved
//
// cm.c
// RAS L2TP WAN mini-port/call-manager driver
// Call Manager routines
//
// 01/07/97 Steve Cobb
#include "l2tpp.h"
#include "cm.tmh"
// Debug counts of client oddities that should not be happening.
//
ULONG g_ulUnexpectedInCallCompletes = 0; ULONG g_ulCallsNotClosable = 0; ULONG g_ulCompletingVcCorruption = 0;
//-----------------------------------------------------------------------------
// Local prototypes (alphabetically)
//-----------------------------------------------------------------------------
VOID BuildCallParametersShell( IN ADAPTERCB* pAdapter, IN ULONG ulIpAddress, IN ULONG ulBufferLength, OUT CHAR* pBuffer, OUT CO_AF_TAPI_INCOMING_CALL_PARAMETERS UNALIGNED** ppTiParams, OUT LINE_CALL_INFO** ppTcInfo, OUT L2TP_CALL_PARAMETERS** ppLcParams );
VOID CallSetupComplete( IN VCCB* pVc );
TUNNELCB* CreateTunnelCb( IN ADAPTERCB* pAdapter );
VOID InactiveCallCleanUp( IN VCCB* pVc );
VOID IncomingCallCompletePassive( IN TUNNELWORK* pWork, IN TUNNELCB* pTunnel, IN VCCB* pVc, IN ULONG_PTR* punpArgs );
VOID DereferenceAf( IN ADAPTERCB* pAdapter );
VOID DeregisterSapPassive( IN NDIS_WORK_ITEM* pWork, IN VOID* pContext );
VOID LockIcs( IN VCCB* pVc, IN BOOLEAN fGrace );
NDIS_STATUS QueryCmInformation( IN ADAPTERCB* pAdapter, IN VCCB* pVc, IN NDIS_OID Oid, IN PVOID InformationBuffer, IN ULONG InformationBufferLength, OUT PULONG BytesWritten, OUT PULONG BytesNeeded );
VOID ReferenceAf( IN ADAPTERCB* pAdapter );
VOID RegisterSapPassive( IN NDIS_WORK_ITEM* pWork, IN VOID* pContext );
VOID SetupVcComplete( IN TUNNELCB* pTunnel, IN VCCB* pVc );
VOID TimerQTerminateComplete( IN TIMERQ* pTimerQ, IN VOID* pContext );
VOID TunnelTqTerminateComplete( IN TIMERQ* pTimerQ, IN VOID* pContext );
VOID UnlockIcs( IN VCCB* pVc, IN BOOLEAN fGrace );
//-----------------------------------------------------------------------------
// Call-manager handlers and completers
//-----------------------------------------------------------------------------
NDIS_STATUS LcmCmOpenAf( IN NDIS_HANDLE CallMgrBindingContext, IN PCO_ADDRESS_FAMILY AddressFamily, IN NDIS_HANDLE NdisAfHandle, OUT PNDIS_HANDLE CallMgrAfContext )
// Standard 'CmCmOpenAfHandler' routine called by NDIS when a client
// requests to open an address family. See DDK doc.
//
{ ADAPTERCB* pAdapter; NDIS_HANDLE hExistingAf;
TRACE( TL_I, TM_Cm, ( "LcmCmOpenAf" ) );
pAdapter = (ADAPTERCB* )CallMgrBindingContext; if (pAdapter->ulTag != MTAG_ADAPTERCB) { ASSERT( !"Atag?" ); return NDIS_STATUS_INVALID_DATA; }
if (AddressFamily->AddressFamily != CO_ADDRESS_FAMILY_TAPI_PROXY || AddressFamily->MajorVersion != NDIS_MajorVersion || AddressFamily->MinorVersion != NDIS_MinorVersion) { return NDIS_STATUS_BAD_VERSION; }
// Save NDIS's AF handle in the adapter control block. Interlock just in
// case multiple clients attempt to open the AF, though don't expect this.
//
hExistingAf = InterlockedCompareExchangePointer( &pAdapter->NdisAfHandle, NdisAfHandle, NULL ); if (hExistingAf) { // Our AF has already been opened. Don't accept another open since
// only only one would be able to register a SAP anyway. This way we
// don't have to be in the business of tracking multiple AF handles.
//
ASSERT( !"AF exists?" ); return NDIS_STATUS_FAILURE; }
ReferenceAdapter( pAdapter ); ReferenceAf( pAdapter );
// Since we support only a single address family, just return the adapter
// as the address family context.
//
*CallMgrAfContext = (PNDIS_HANDLE )pAdapter;
TRACE( TL_I, TM_Cm, ( "LcmCmOpenAf OK" ) ); return NDIS_STATUS_SUCCESS; }
NDIS_STATUS LcmCmCloseAf( IN NDIS_HANDLE CallMgrAfContext )
// Standard 'CmCloseAfHandler' routine called by NDIS when a client
// requests to close an address family. See DDK doc.
//
{ ADAPTERCB* pAdapter;
TRACE( TL_I, TM_Cm, ( "LcmCmCloseAf" ) );
pAdapter = (ADAPTERCB* )CallMgrAfContext; if (pAdapter->ulTag != MTAG_ADAPTERCB) { ASSERT( !"Atag?" ); return NDIS_STATUS_INVALID_DATA; }
// This dereference will eventually lead to us calling
// NdisMCmCloseAfComplete.
//
DereferenceAf( pAdapter );
TRACE( TL_V, TM_Cm, ( "LcmCmCloseAf pending" ) ); return NDIS_STATUS_PENDING; }
NDIS_STATUS LcmCmRegisterSap( IN NDIS_HANDLE CallMgrAfContext, IN PCO_SAP Sap, IN NDIS_HANDLE NdisSapHandle, OUT PNDIS_HANDLE CallMgrSapContext )
// Standard 'LcmCmRegisterSapHandler' routine called by NDIS when the a
// client registers a service access point. See DDK doc.
//
{ NDIS_STATUS status; ADAPTERCB* pAdapter; BOOLEAN fSapExists; BOOLEAN fInvalidSapData;
TRACE( TL_I, TM_Cm, ( "LcmCmRegSap" ) );
pAdapter = (ADAPTERCB* )CallMgrAfContext;
// Our SAP context is just the address of the owning adapter control
// block. Set it now before scheduling work as NDIS doesn't handle the
// case of SAP completion correctly otherwise (though it should).
//
*CallMgrSapContext = (NDIS_HANDLE )pAdapter;
if (pAdapter->ulTag != MTAG_ADAPTERCB) { ASSERT( !"Atag?" ); return NDIS_STATUS_INVALID_DATA; }
NdisAcquireSpinLock( &pAdapter->lockSap ); { if (pAdapter->NdisSapHandle) { fSapExists = TRUE; } else { // Save NDIS's SAP handle in the adapter control block.
//
fSapExists = FALSE;
// Extract the SAP line and address IDs and store for
// regurgitation in incoming call dispatches.
//
if (Sap->SapType == AF_TAPI_SAP_TYPE && Sap->SapLength >= sizeof(CO_AF_TAPI_SAP)) { CO_AF_TAPI_SAP* pSap;
pSap = (CO_AF_TAPI_SAP* )(Sap->Sap); pAdapter->ulSapLineId = pSap->ulLineID;
if (pSap->ulAddressID == 0xFFFFFFFF) { // This means "any ID is OK" but when indicated back up
// NDPROXY doesn't recognize this code, so translate it to
// 0 here.
//
pAdapter->ulSapAddressId = 0; } else { pAdapter->ulSapAddressId = pSap->ulAddressID; }
pAdapter->NdisSapHandle = NdisSapHandle; fInvalidSapData = FALSE; } else { fInvalidSapData = TRUE; } } } NdisReleaseSpinLock( &pAdapter->lockSap );
if (fSapExists) { TRACE( TL_A, TM_Cm, ( "SAP exists?" ) ); WPLOG( LL_A, LM_Cm, ( "SAP exists?" ) ); return NDIS_STATUS_SAP_IN_USE; }
if (fInvalidSapData) { TRACE( TL_A, TM_Cm, ( "SAP data?" ) ); WPLOG( LL_A, LM_Cm, ( "SAP data?" ) ); return NDIS_STATUS_INVALID_DATA; }
// TDI setup must be done at PASSIVE IRQL so schedule a routine to do it.
//
status = ScheduleWork( pAdapter, RegisterSapPassive, pAdapter ); if (status != NDIS_STATUS_SUCCESS) { NdisAcquireSpinLock( &pAdapter->lockSap ); { pAdapter->NdisSapHandle = NULL; } NdisReleaseSpinLock( &pAdapter->lockSap ); return status; }
TRACE( TL_V, TM_Cm, ( "LcmCmRegSap pending" ) ); return NDIS_STATUS_PENDING; }
VOID RegisterSapPassive( IN NDIS_WORK_ITEM* pWork, IN VOID* pContext )
// An NDIS_PROC routine to complete the registering of a SAP begun in
// LcmCmRegisterSap.
//
{ NDIS_STATUS status; ADAPTERCB* pAdapter; NDIS_HANDLE hSap;
TRACE( TL_N, TM_Cm, ( "RegSapPassive" ) );
// Unpack context information then free the work item.
//
pAdapter = (ADAPTERCB* )pContext; ASSERT( pAdapter->ulTag == MTAG_ADAPTERCB ); FREE_NDIS_WORK_ITEM( pAdapter, pWork );
// Open the TDI transport and start receiving datagrams.
//
status = TdixOpen( &pAdapter->tdix );
NdisAcquireSpinLock( &pAdapter->lockSap ); { hSap = pAdapter->NdisSapHandle;
if (status == NDIS_STATUS_SUCCESS) { // Mark the SAP active allowing references to be taken, and take
// the initial reference for SAP registry, plus those for address
// family and adapter.
//
SetFlags( &pAdapter->ulFlags, ACBF_SapActive ); ASSERT( pAdapter->lSapRef == 0 ); TRACE( TL_N, TM_Ref, ( "RefSap-ish to 1" ) ); pAdapter->lSapRef = 1; ReferenceAdapter( pAdapter ); ReferenceAf( pAdapter ); } else { // Failed to get TDI set up, so NULL the SAP handle in the adapter
// control block.
//
TRACE( TL_A, TM_Cm, ( "TdixOpen=$%08x?", status ) ); WPLOG( LL_A, LM_Cm, ( "TdixOpen=$%08x?", status ) ); pAdapter->NdisSapHandle = NULL; } } NdisReleaseSpinLock( &pAdapter->lockSap );
// Remove the reference for scheduled work. Do this before telling NDIS
// the SAP completed because if it failed it can call Halt and unload the
// driver before we run again here which gives a C4 bugcheck.
//
DereferenceAdapter( pAdapter );
// Report result to client.
//
TRACE( TL_I, TM_Cm, ( "NdisMCmRegSapComp" ) ); NdisMCmRegisterSapComplete( status, hSap, (NDIS_HANDLE )pAdapter ); TRACE( TL_I, TM_Cm, ( "NdisMCmRegSapComp done" ) ); }
NDIS_STATUS LcmCmDeregisterSap( NDIS_HANDLE CallMgrSapContext )
// Standard 'CmDeregisterSapHandler' routine called by NDIS when the a
// client has requested to de-register a service access point. See DDK
// doc.
//
{ NDIS_STATUS status; ADAPTERCB* pAdapter;
TRACE( TL_I, TM_Cm, ( "LcmCmDeregSap" ) );
pAdapter = (ADAPTERCB* )CallMgrSapContext; if (pAdapter->ulTag != MTAG_ADAPTERCB) { ASSERT( !"Atag?" ); return NDIS_STATUS_INVALID_DATA; }
NdisAcquireSpinLock( &pAdapter->lockSap ); { if (ReadFlags( &pAdapter->ulFlags ) & ACBF_SapActive) { ASSERT( pAdapter->NdisSapHandle ); ClearFlags( &pAdapter->ulFlags, ACBF_SapActive ); status = NDIS_STATUS_PENDING; } else { TRACE( TL_A, TM_Cm, ( "No SAP active?" ) ); WPLOG( LL_A, LM_Cm, ( "No SAP active?" ) ); status = NDIS_STATUS_FAILURE; } } NdisReleaseSpinLock( &pAdapter->lockSap );
if (status == NDIS_STATUS_PENDING) { // Remove the reference for SAP registry. Eventually, the SAP
// references will fall to 0 and DereferenceSap will schedule
// DeregisterSapPassive to complete the de-registry.
//
DereferenceSap( pAdapter ); }
TRACE( TL_V, TM_Cm, ( "LcmCmDeregSap=$%08x", status ) ); return status; }
VOID DeregisterSapPassive( IN NDIS_WORK_ITEM* pWork, IN VOID* pContext )
// An NDIS_PROC routine to complete the de-registering of a SAP begun in
// LcmCmDeregisterSap.
//
{ ADAPTERCB* pAdapter; NDIS_HANDLE hOldSap;
TRACE( TL_I, TM_Cm, ( "DeregSapPassive" ) );
// Unpack context information then free the work item.
//
pAdapter = (ADAPTERCB* )pContext; ASSERT( pAdapter->ulTag == MTAG_ADAPTERCB ); FREE_NDIS_WORK_ITEM( pAdapter, pWork );
// Stop receiving datagrams (at least on behalf of this SAP) and
// deregister the SAP.
//
NdisAcquireSpinLock( &pAdapter->lockSap ); { hOldSap = pAdapter->NdisSapHandle; pAdapter->NdisSapHandle = NULL; } NdisReleaseSpinLock( &pAdapter->lockSap );
TdixClose( &pAdapter->tdix );
// Remove the adapter references for the NdisSapHandle and for scheduled
// work. Remove the address family reference for the NdisSapHandle. Do
// all this before telling NDIS the deregister has completed because it
// can call Halt and unload the driver before we run again here giving a
// C4 bugcheck.
//
DereferenceAdapter( pAdapter ); DereferenceAdapter( pAdapter ); DereferenceAf( pAdapter );
// Report result to client.
//
TRACE( TL_I, TM_Cm, ( "NdisMCmDeregSapComp" ) ); NdisMCmDeregisterSapComplete( NDIS_STATUS_SUCCESS, hOldSap ); TRACE( TL_I, TM_Cm, ( "NdisMCmDeregSapComp done" ) ); }
NDIS_STATUS LcmCmCreateVc( IN NDIS_HANDLE ProtocolAfContext, IN NDIS_HANDLE NdisVcHandle, OUT PNDIS_HANDLE ProtocolVcContext )
// Standard 'CmCreateVc' routine called by NDIS in response to a
// client's request to create a virtual circuit. This
// call must return synchronously.
//
{ NDIS_STATUS status; ADAPTERCB* pAdapter; VCCB* pVc;
pAdapter = (ADAPTERCB* )ProtocolAfContext; if (pAdapter->ulTag != MTAG_ADAPTERCB) { ASSERT( !"Atag?" ); return NDIS_STATUS_INVALID_DATA; }
// Allocate and zero a VC control block, then make any non-zero
// initializations.
//
pVc = ALLOC_VCCB( pAdapter ); if (!pVc) { WPLOG( LL_A, LM_Res, ( "Failed to allocate VCCB!" ) ); return NDIS_STATUS_RESOURCES; }
NdisZeroMemory( pVc, sizeof(*pVc) );
TRACE( TL_I, TM_Cm, ( "LcmCmCreateVc $%p", pVc ) ); WPLOG( LL_M, LM_Cm, ( "New CALL %p", pVc ) );
// Zero the back pointer to the tunnel control block (above) and
// initialize the detached link since clean-up may be required before this
// block is ever linked into a tunnel chain.
//
InitializeListHead( &pVc->linkVcs ); InitializeListHead( &pVc->linkRequestingVcs ); InitializeListHead( &pVc->linkCompletingVcs );
// Set a marker for easier memory dump browsing.
//
pVc->ulTag = MTAG_VCCB;
// Save a back pointer to the adapter for use in LcmCmDeleteVc later.
//
ReferenceAdapter( pAdapter ); pVc->pAdapter = pAdapter;
// Initialize the VC and call spinlock and send/receive lists.
//
NdisAllocateSpinLock( &pVc->lockV ); NdisAllocateSpinLock( &pVc->lockCall ); InitializeListHead( &pVc->listSendsOut ); InitializeListHead( &pVc->listOutOfOrder );
// Save the NDIS handle of this VC for use in indications to NDIS later.
//
pVc->NdisVcHandle = NdisVcHandle;
// Initialize the estimated round trip time and send timeout per the
// suggestions in the draft/RFC.
//
pVc->ulRoundTripMs = L2TP_LnsDefaultPpd * 100; pVc->ulSendTimeoutMs = pVc->ulRoundTripMs;
// Initialize link capabilities to the defaults for the adapter.
//
{ NDIS_WAN_CO_INFO* pwci = &pAdapter->info; NDIS_WAN_CO_GET_LINK_INFO* pwcgli = &pVc->linkinfo;
NdisZeroMemory( &pVc->linkinfo, sizeof(pVc->linkinfo) ); pwcgli->MaxSendFrameSize = pwci->MaxFrameSize; pwcgli->MaxRecvFrameSize = pwci->MaxFrameSize; pwcgli->SendFramingBits = pwci->FramingBits; pwcgli->RecvFramingBits = pwci->FramingBits; pwcgli->SendACCM = pwci->DesiredACCM; pwcgli->RecvACCM = pwci->DesiredACCM; }
// Default send window, "slow started". This is typically adjusted based
// on peer's Receive Window AVP when the call is created.
//
pVc->ulSendWindow = pAdapter->info.MaxSendWindow >> 1; if (pVc->ulSendWindow == 0) { pVc->ulSendWindow = 1; }
// The VC control block's address is the VC context we return to NDIS.
//
*ProtocolVcContext = (NDIS_HANDLE )pVc;
// Add a reference to the control block and the associated address family
// that is removed by LmpCoDeleteVc.
//
ReferenceVc( pVc ); ReferenceAf( pAdapter );
TRACE( TL_V, TM_Cm, ( "LcmCmCreateVc=0" ) ); return NDIS_STATUS_SUCCESS; }
NDIS_STATUS LcmCmDeleteVc( IN NDIS_HANDLE ProtocolVcContext )
// Standard 'CmDeleteVc' routine called by NDIS in response to a
// client's request to delete a virtual circuit. This
// call must return synchronously.
//
{ VCCB* pVc;
TRACE( TL_I, TM_Cm, ( "LcmCmDelVc($%p)", ProtocolVcContext ) );
pVc = (VCCB* )ProtocolVcContext; if (pVc->ulTag != MTAG_VCCB) { ASSERT( !"Vtag?" ); WPLOG( LL_A, LM_Cm, ( "VC %p invalid tag?", pVc ) ); return NDIS_STATUS_INVALID_DATA; }
// This flag catches attempts by the client to delete the VC twice.
//
if (ReadFlags( &pVc->ulFlags ) & VCBF_VcDeleted) { TRACE( TL_A, TM_Cm, ( "VC $%p re-deleted?", pVc ) ); WPLOG( LL_A, LM_Cm, ( "VC %p re-deleted?", pVc ) ); return NDIS_STATUS_FAILURE; }
WPLOG( LL_M, LM_Cm, ( "Free CALL %p, Cid %d, Peer's Cid %d", pVc, pVc->usCallId, pVc->usAssignedCallId ) ); SetFlags( &pVc->ulFlags, VCBF_VcDeleted );
// Remove the references added by LcmCmCreateVc.
//
DereferenceAf( pVc->pAdapter ); DereferenceVc( pVc );
TRACE( TL_V, TM_Cm, ( "LcmCmDelVc=0" ) ); return NDIS_STATUS_SUCCESS; }
NDIS_STATUS LcmCmMakeCall( IN NDIS_HANDLE CallMgrVcContext, IN OUT PCO_CALL_PARAMETERS CallParameters, IN NDIS_HANDLE NdisPartyHandle, OUT PNDIS_HANDLE CallMgrPartyContext )
// Standard 'CmMakeCallHandler' routine called by NDIS when the a client
// has requested to connect to a remote end-point. See DDK doc.
//
{ NDIS_STATUS status; CO_SPECIFIC_PARAMETERS* pMSpecifics; CO_AF_TAPI_MAKE_CALL_PARAMETERS UNALIGNED* pTmParams; LINE_CALL_PARAMS* pTcParams; L2TP_CALL_PARAMETERS* pLcParams; VCCB* pVc; TUNNELCB* pTunnel; ADAPTERCB* pAdapter; ULONG ulIpAddress; BOOLEAN fDefaultLcParams; BOOLEAN fExclusiveTunnel;
TRACE( TL_I, TM_Cm, ( "LcmCmMakeCall" ) );
pVc = (VCCB* )CallMgrVcContext; if (pVc->ulTag != MTAG_VCCB) { ASSERT( "!Vtag?" ); WPLOG( LL_A, LM_Cm, ( "Vtag? %p?", pVc ) ); return NDIS_STATUS_INVALID_DATA; }
pAdapter = pVc->pAdapter;
// L2TP has no concept of point-to-multi-point "parties".
//
if (CallMgrPartyContext) { *CallMgrPartyContext = NULL; }
// Validate call parameters.
//
do { // Validate base call parameters.
//
{ // L2TP provides switched VCs only.
//
if (CallParameters->Flags & (PERMANENT_VC | BROADCAST_VC | MULTIPOINT_VC)) { status = NDIS_STATUS_NOT_SUPPORTED; break; }
// We're supposed to set CALL_PARAMETERS_CHANGED on return if we
// changed the call parameters, leaving a catch-22 if caller
// already has it set. Also, for TAPI address family, media call
// parameters must be present, though call manager call parameters
// are not.
//
if ((CallParameters->Flags & CALL_PARAMETERS_CHANGED) || !CallParameters->MediaParameters) { status = NDIS_STATUS_INVALID_DATA; break; }
pMSpecifics = &CallParameters->MediaParameters->MediaSpecific; if (pMSpecifics->Length < sizeof(CO_AF_TAPI_MAKE_CALL_PARAMETERS)) { status = NDIS_STATUS_INVALID_DATA; break; }
pTmParams = (CO_AF_TAPI_MAKE_CALL_PARAMETERS UNALIGNED* )&pMSpecifics->Parameters;
if (pTmParams->LineCallParams.Length < sizeof(LINE_CALL_PARAMS)) { status = NDIS_STATUS_INVALID_DATA; break; }
pTcParams = (LINE_CALL_PARAMS* ) (((CHAR UNALIGNED* )&pTmParams->LineCallParams) + pTmParams->LineCallParams.Offset); }
// Validate call parameters.
//
{ CHAR* pszAddress;
// Caller must provide a destination IP address. The address is
// ANSI as are all non-format-coded strings to/from TAPI.
//
pszAddress = StrDupNdisVarDataDescStringToA( &pTmParams->DestAddress ); if (!pszAddress) { status = NDIS_STATUS_RESOURCES; break; }
ulIpAddress = IpAddressFromDotted( pszAddress ); FREE_NONPAGED( pszAddress ); if (ulIpAddress == 0 || IPADDR_IS_BROADCAST(ulIpAddress) || IPADDR_IS_MULTICAST(ulIpAddress)) { status = NDIS_STATUS_INVALID_ADDRESS; break; }
// Reject if unknown WAN-type bits are set.
//
if (pTcParams->ulMediaMode & ~(LINEMEDIAMODE_DATAMODEM | LINEMEDIAMODE_DIGITALDATA)) { status = NDIS_STATUS_INVALID_DATA; break; } }
// Validate L2TP call parameters.
//
// When caller doesn't provide L2TP-specific parameters a local block
// with default values is substituted for the convenience of the rest
// of the code.
//
{ if (pTcParams->ulDevSpecificSize == sizeof(*pLcParams)) { pLcParams = (L2TP_CALL_PARAMETERS* ) ((CHAR* )pTcParams) + pTcParams->ulDevSpecificOffset; fDefaultLcParams = FALSE; } else { pLcParams = (L2TP_CALL_PARAMETERS* )ALLOC_NONPAGED( sizeof(*pLcParams), MTAG_L2TPPARAMS ); if (!pLcParams) { WPLOG( LL_A, LM_Res, ( "Failed to allocate L2TP_CALL_PARAMETERS") ); status = NDIS_STATUS_RESOURCES; break; }
fDefaultLcParams = TRUE; NdisZeroMemory( pLcParams, sizeof(*pLcParams) ); pLcParams->ulPhysicalChannelId = 0xFFFFFFFF; } }
status = NDIS_STATUS_SUCCESS; } while (FALSE);
if (status != NDIS_STATUS_SUCCESS) { return status; }
// Stash the call parameters in the VC block. Simultaneous MakeCalls on
// the same VC is a client error, but it's easy to guard against so do
// that here.
//
if (InterlockedCompareExchangePointer( &pVc->pMakeCall, CallParameters, NULL )) { ASSERT( !"Double MakeCall?" ); if (fDefaultLcParams) { FREE_NONPAGED( pLcParams ); } return NDIS_STATUS_CALL_ACTIVE; }
pVc->pTmParams = pTmParams; pVc->pTcParams = pTcParams; pVc->pLcParams = pLcParams;
// This VC's call is now cleanable, i.e. the base call clean up routine,
// InactiveCallCleanUp, will now eventually be called.
//
do { // Convert parameter and configuration information to VC flags where
// appropriate.
//
{ ULONG ulMask = 0;
if (CallParameters->MediaParameters->Flags & RECEIVE_TIME_INDICATION) { ulMask |= VCBF_IndicateTimeReceived; }
if (pAdapter->ulFlags & ACBF_OutgoingRoleLac) { ulMask |= VCBF_IncomingFsm; }
if (fDefaultLcParams) { ulMask |= VCBF_DefaultLcParams; }
if (ulMask) { SetFlags( &pVc->ulFlags, ulMask ); } }
// Take the next progressively increasing call serial number string.
//
NdisInterlockedIncrement( &pAdapter->ulCallSerialNumber );
// Reserve a Call-ID slot in the adapter's table.
//
status = ReserveCallIdSlot( pVc ); if (status != NDIS_STATUS_SUCCESS) { break; }
// Create a new or find an existing tunnel control block for caller's
// specified IP address in the adapter's list. The returned block is
// linked to the adapter and referenced. The reference is the one for
// linkage in the list, i.e. case (a).
//
fExclusiveTunnel = (BOOLEAN ) ((fDefaultLcParams) ? !!(pAdapter->ulFlags & ACBF_ExclusiveTunnels) : !!(pLcParams->ulFlags & L2TPCPF_ExclusiveTunnel));
pTunnel = SetupTunnel( pAdapter, ulIpAddress, 0, 0, fExclusiveTunnel ); if (!pTunnel) { status = NDIS_STATUS_RESOURCES; break; }
NdisAcquireSpinLock( &pTunnel->lockT ); { if (ReadFlags( &pTunnel->ulFlags ) & TCBF_Closing) { // This is unlikely because SetupTunnel only finds non-closing
// tunnels, but this check and linkage must occur atomically
// under 'lockT'. New VCs must not be linked onto closing
// tunnels.
//
status = NDIS_STATUS_TAPI_DISCONNECTMODE_UNKNOWN; } else { // The call has an open operation pending and can accept close
// requests.
//
SetFlags( &pVc->ulFlags, VCBF_ClientOpenPending | VCBF_CallClosableByClient | VCBF_CallClosableByPeer );
NdisAcquireSpinLock( &pTunnel->lockVcs ); { // Set the back pointer to it's tunnel. The associated
// tunnel reference was taken by SetupTunnel above.
//
pVc->pTunnel = pTunnel;
// Link the VC into the tunnel's list of associated VCs.
//
InsertTailList( &pTunnel->listVcs, &pVc->linkVcs ); } NdisReleaseSpinLock( &pTunnel->lockVcs ); } } NdisReleaseSpinLock( &pTunnel->lockT ); } while (FALSE);
if (status != NDIS_STATUS_SUCCESS) { CallCleanUp( pVc ); return status; }
// Schedule FsmOpenTunnel to kick off the combination of tunnel and call
// creation state machines that will eventually call NdisMakeCallComplete
// to notify caller of the result. A happy side effect of the scheduling
// is that the callback will occur at PASSIVE IRQL, the level at which TDI
// clients must run.
//
pVc->state = CS_WaitTunnel; ScheduleTunnelWork( pTunnel, pVc, FsmOpenTunnel, 0, 0, 0, 0, FALSE, FALSE );
TRACE( TL_V, TM_Cm, ( "LcmCmMakeCall pending" ) ); return NDIS_STATUS_PENDING; }
NDIS_STATUS LcmCmCloseCall( IN NDIS_HANDLE CallMgrVcContext, IN NDIS_HANDLE CallMgrPartyContext, IN PVOID CloseData, IN UINT Size )
// Standard 'CmCloseCallHandler' routine called by NDIS when the a client
// has requested to tear down a call. See DDK doc.
//
{ NDIS_STATUS status; ADAPTERCB* pAdapter; VCCB* pVc; ULONG ulFlags; BOOLEAN fCallClosable;
TRACE( TL_I, TM_Cm, ( "LcmCmCloseCall($%p)", CallMgrVcContext ) );
pVc = (VCCB* )CallMgrVcContext; if (pVc->ulTag != MTAG_VCCB) { ASSERT( !"Vtag?" ); return NDIS_STATUS_INVALID_DATA; }
pAdapter = pVc->pAdapter;
NdisAcquireSpinLock( &pVc->lockV ); { ulFlags = ReadFlags( &pVc->ulFlags );
if (ulFlags & VCBF_WaitCloseCall) { // Note that we got the close from the client we were expecting.
// This is helpful information when debugging, but is not
// otherwise used.
//
ClearFlags( &pVc->ulFlags, VCBF_WaitCloseCall ); }
if (ulFlags & VCBF_CallClosableByClient) { fCallClosable = TRUE;
// Accepting this close makes the call no longer closable by
// client or peer. Any peer operation that was pending is
// cleared, and a client close becomes pending. It is possible to
// have both a client open and close pending at the same time.
//
ClearFlags( &pVc->ulFlags, (VCBF_CallClosableByClient | VCBF_CallClosableByPeer | VCBF_PeerClosePending | VCBF_PeerOpenPending) ); SetFlags( &pVc->ulFlags, VCBF_ClientClosePending );
// If a client open is pending, it fails.
//
if (ulFlags & VCBF_ClientOpenPending) { pVc->status = NDIS_STATUS_TAPI_DISCONNECTMODE_NORMAL; }
// Close the call, being graceful if possible.
//
ASSERT( pVc->pTunnel ); ScheduleTunnelWork( pVc->pTunnel, pVc, FsmCloseCall, (ULONG_PTR )CRESULT_Administrative, (ULONG_PTR )GERR_None, 0, 0, FALSE, FALSE ); } else { TRACE( TL_A, TM_Cm, ( "Call not closable!" ) ); WPLOG( LL_A, LM_Cm, ( "Call not closable, pVc = %p!", pVc ) ); fCallClosable = FALSE; } } NdisReleaseSpinLock( &pVc->lockV );
if (!fCallClosable) { // The call is not in a closable state. Just fail the request
// immediately. Since the docs say the call must return PENDING, this
// is done by calling the completion routine here, in typical NDIS
// fashion.
//
++g_ulCallsNotClosable; TRACE( TL_A, TM_Cm, ( "Call NdisMCmCloseCallComp(FAIL)!" ) ); WPLOG( LL_A, LM_Cm, ( "Call NdisMCmCloseCallComp(FAIL)!" ) ); NdisMCmCloseCallComplete( NDIS_STATUS_FAILURE, pVc->NdisVcHandle, NULL ); TRACE( TL_I, TM_Cm, ( "NdisMCmCloseCallComp done" ) );
// Careful, client may have deleted the VC, so 'pVc' must not be
// referenced hereafter.
//
}
TRACE( TL_V, TM_Cm, ( "LcmCmCloseCall pending" ) ); return NDIS_STATUS_PENDING; }
VOID LcmCmIncomingCallComplete( IN NDIS_STATUS Status, IN NDIS_HANDLE CallMgrVcContext, IN PCO_CALL_PARAMETERS CallParameters )
// Standard 'CmIncomingCallCompleteHandler' routine called by NDIS when
// a client has responded to the call-managers's previously dispatched
// incoming call. See DDK doc.
//
{ VCCB* pVc;
TRACE( TL_I, TM_Cm, ( "LcmCmInCallComp($%p,s=$%08x)", CallMgrVcContext, Status ) );
pVc = (VCCB* )CallMgrVcContext; if (pVc->ulTag != MTAG_VCCB) { ASSERT( !"VTag" ); return; }
// The work is scheduled to avoid a possible recursive loop of completing
// VCs that could overrun the stack. See bug 370996.
//
ASSERT( pVc->pTunnel ); ScheduleTunnelWork( pVc->pTunnel, pVc, IncomingCallCompletePassive, (ULONG )Status, 0, 0, 0, FALSE, FALSE );
TRACE( TL_V, TM_Cm, ( "LcmCmInCallComp done" ) ); }
VOID IncomingCallCompletePassive( IN TUNNELWORK* pWork, IN TUNNELCB* pTunnel, IN VCCB* pVc, IN ULONG_PTR* punpArgs )
// A PTUNNELWORK routine to complete an LcmCmIncomingCallComplete.
//
// This routine is called only at PASSIVE IRQL.
//
{ NDIS_STATUS status; ADAPTERCB* pAdapter;
TRACE( TL_N, TM_Cm, ( "InCallCompApc" ) );
// Unpack context information then free the work item.
//
pAdapter = pVc->pAdapter; status = (NDIS_STATUS )(punpArgs[ 0 ]); FREE_TUNNELWORK( pAdapter, pWork );
// Guard against a double-complete error by the client.
//
if (ReadFlags( &pVc->ulFlags ) & VCBF_WaitInCallComplete) { ClearFlags( &pVc->ulFlags, VCBF_WaitInCallComplete );
if (status != NDIS_STATUS_SUCCESS) { pVc->usResult = CRESULT_Busy; pVc->usError = GERR_None;
// Turn off the "call NdisMCmDispatchIncomingCloseCall if peer
// terminates the call" flag. It was turned on even though peer
// pended, per JameelH.
//
ClearFlags( &pVc->ulFlags, VCBF_VcDispatched ); WPLOG( LL_A, LM_Cm, ( "Failed pVc = %p,s= %08x!)", pVc, status ) ); }
SetupVcComplete( pTunnel, pVc ); } else { ASSERT( !"Not expecting InCallComp?" ); ++g_ulUnexpectedInCallCompletes; }
// Remove the VC and call references covering the dispatched incoming
// call.
//
DereferenceCall( pVc ); DereferenceVc( pVc ); }
VOID LcmCmActivateVcComplete( IN NDIS_STATUS Status, IN NDIS_HANDLE CallMgrVcContext, IN PCO_CALL_PARAMETERS CallParameters )
// Standard 'CmActivateVcCompleteHandler' routine called by NDIS when the
// mini-port has completed the call-manager's previous request to activate
// a virtual circuit. See DDK doc.
//
{ ASSERT( !"LcmCmActVcComp?" ); }
VOID LcmCmDeactivateVcComplete( IN NDIS_STATUS Status, IN NDIS_HANDLE CallMgrVcContext )
// Standard 'CmDeactivateVcCompleteHandler' routine called by NDIS when
// the mini-port has completed the call-manager's previous request to
// de-activate a virtual circuit. See DDK doc.
//
{ ASSERT( !"LcmCmDeactVcComp?" ); }
NDIS_STATUS LcmCmModifyCallQoS( IN NDIS_HANDLE CallMgrVcContext, IN PCO_CALL_PARAMETERS CallParameters )
// Standard 'CmModifyQoSCallHandler' routine called by NDIS when a client
// requests a modification in the quality of service provided by the
// virtual circuit. See DDK doc.
//
{ TRACE( TL_N, TM_Cm, ( "LcmCmModQoS" ) );
// There is no useful concept of quality of service for IP media.
//
return NDIS_STATUS_NOT_SUPPORTED; }
NDIS_STATUS LcmCmRequest( IN NDIS_HANDLE CallMgrAfContext, IN NDIS_HANDLE CallMgrVcContext, IN NDIS_HANDLE CallMgrPartyContext, IN OUT PNDIS_REQUEST NdisRequest )
// Standard 'CmRequestHandler' routine called by NDIS in response to a
// client's request for information from the call manager.
//
{ ADAPTERCB* pAdapter; VCCB* pVc; NDIS_STATUS status;
TRACE( TL_I, TM_Cm, ( "LcmCmReq" ) );
pAdapter = (ADAPTERCB* )CallMgrAfContext; if (pAdapter->ulTag != MTAG_ADAPTERCB) { ASSERT( !"Atag?" ); return NDIS_STATUS_INVALID_DATA; }
pVc = (VCCB* )CallMgrVcContext; if (pVc && pVc->ulTag != MTAG_VCCB) { ASSERT( !"Vtag?" ); return NDIS_STATUS_INVALID_DATA; }
switch (NdisRequest->RequestType) { case NdisRequestQueryInformation: { status = QueryCmInformation( pAdapter, pVc, NdisRequest->DATA.QUERY_INFORMATION.Oid, NdisRequest->DATA.QUERY_INFORMATION.InformationBuffer, NdisRequest->DATA.QUERY_INFORMATION.InformationBufferLength, &NdisRequest->DATA.QUERY_INFORMATION.BytesWritten, &NdisRequest->DATA.QUERY_INFORMATION.BytesNeeded ); break; }
case NdisRequestSetInformation: { TRACE( TL_A, TM_Cm, ( "CmSetOID=%d?", NdisRequest->DATA.SET_INFORMATION.Oid ) ); status = NDIS_STATUS_NOT_SUPPORTED; break; }
default: { status = NDIS_STATUS_NOT_SUPPORTED; TRACE( TL_A, TM_Cm, ( "CmType=%d?", NdisRequest->RequestType ) ); break; } }
return status; }
//-----------------------------------------------------------------------------
// Call utility routines (alphabetically)
// Some are used externally
//-----------------------------------------------------------------------------
VOID ActivateCallIdSlot( IN VCCB* pVc )
// Sets the address of the VC, 'pVc', in the adapter's table of Call-IDs
// enabling receives on the Call-ID.
//
{ ADAPTERCB* pAdapter;
pAdapter = pVc->pAdapter;
if (pVc->usCallId > 0 && pVc->usCallId <= pAdapter->usMaxVcs) { ASSERT( pAdapter->ppVcs[ pVc->usCallId - 1 ] == (VCCB* )-1 );
NdisAcquireSpinLock( &pAdapter->lockVcs ); { pAdapter->ppVcs[ pVc->usCallId - 1 ] = pVc; } NdisReleaseSpinLock( &pAdapter->lockVcs ); } }
VOID BuildCallParametersShell( IN ADAPTERCB* pAdapter, IN ULONG ulIpAddress, IN ULONG ulBufferLength, OUT CHAR* pBuffer, OUT CO_AF_TAPI_INCOMING_CALL_PARAMETERS UNALIGNED ** ppTiParams, OUT LINE_CALL_INFO** ppTcInfo, OUT L2TP_CALL_PARAMETERS** ppLcParams )
// Loads caller's buffer 'pBuffer' of length 'ulBufferLength' bytes with a
// CO_CALL_PARAMETERS structure containing default values. Loads caller's
// '*ppTiParams', '*ppTcInfo', and '*ppLcParams' with shortcut pointers to
// the TAPI call and L2TP specific structures within the built
// CO_CALL_PARAMETERS. 'PAdapter' is the adapter context. 'pUlIpAddress'
// is the IP address of the peer in network byte order.
//
{ CO_CALL_PARAMETERS* pCp; CO_CALL_MANAGER_PARAMETERS* pCmp; CO_MEDIA_PARAMETERS* pMp; CO_AF_TAPI_INCOMING_CALL_PARAMETERS UNALIGNED * pTip; LINE_CALL_INFO* pLci; L2TP_CALL_PARAMETERS* pLcp; CHAR* pszCallerId; ULONG ulLciTotalSize; ULONG ulMediaSpecificSize; ULONG ulBytesPerSec; WCHAR* pszCallerID;
NdisZeroMemory( pBuffer, ulBufferLength );
pCp = (CO_CALL_PARAMETERS* )pBuffer; pCmp = (PCO_CALL_MANAGER_PARAMETERS ) ( (PUCHAR)(pCp + 1) + sizeof(PVOID) ); (ULONG_PTR) pCmp &= ~( (ULONG_PTR) sizeof(PVOID) - 1 ); pCp->CallMgrParameters = pCmp; pMp = (PCO_MEDIA_PARAMETERS ) ( (PUCHAR) (pCmp + 1) + sizeof(PVOID) ); (ULONG_PTR) pMp &= ~( (ULONG_PTR) sizeof(PVOID) - 1 ); pCp->MediaParameters = pMp;
// This needs to be dynamic based on speed reported by TDI.
//
ulBytesPerSec = L2TP_LanBps / 8; pCmp->Transmit.TokenRate = ulBytesPerSec; pCmp->Transmit.PeakBandwidth = ulBytesPerSec; pCmp->Transmit.MaxSduSize = L2TP_MaxFrameSize; pCmp->Receive.TokenRate = ulBytesPerSec; pCmp->Receive.PeakBandwidth = ulBytesPerSec; pCmp->Receive.MaxSduSize = L2TP_MaxFrameSize;
ulLciTotalSize = sizeof(*pLci) + sizeof(PVOID) + sizeof(*pLcp) + ((L2TP_MaxDottedIpLen + 1) * sizeof(WCHAR));
ulMediaSpecificSize = sizeof(*pTip) + sizeof(PVOID) + ulLciTotalSize;
pTip = (CO_AF_TAPI_INCOMING_CALL_PARAMETERS UNALIGNED* )pMp->MediaSpecific.Parameters;
pLci = (LINE_CALL_INFO*) ( (PUCHAR) (pTip + 1) + sizeof(PVOID) ); (ULONG_PTR) pLci &= ~( (ULONG_PTR) sizeof(PVOID) - 1 );
pLcp = (L2TP_CALL_PARAMETERS*) ( (PUCHAR) (pLci + 1) + sizeof(PVOID) ); (ULONG_PTR) pLcp &= ~( (ULONG_PTR) sizeof(PVOID) - 1 );
pMp->ReceiveSizeHint = L2TP_MaxFrameSize; pMp->MediaSpecific.Length = ulMediaSpecificSize; pTip->LineCallInfo.Length = (USHORT )ulLciTotalSize; pTip->LineCallInfo.MaximumLength = (USHORT )ulLciTotalSize; pTip->LineCallInfo.Offset = (ULONG) ((CHAR*) pLci - (CHAR*) &pTip->LineCallInfo);
pLci->ulTotalSize = ulLciTotalSize; pLci->ulNeededSize = ulLciTotalSize; pLci->ulUsedSize = ulLciTotalSize; pLci->ulLineDeviceID = pAdapter->ulSapLineId; pLci->ulAddressID = pAdapter->ulSapAddressId; pLci->ulDevSpecificSize = sizeof(*pLcp); pLci->ulDevSpecificOffset = (ULONG) ((CHAR*) pLcp - (CHAR*) pLci); pLci->ulBearerMode = LINEBEARERMODE_DATA;
pLci->ulCallerIDOffset = pLci->ulDevSpecificOffset + pLci->ulDevSpecificSize; pszCallerID = (WCHAR*)(((CHAR* )pLci) + pLci->ulCallerIDOffset); DottedFromIpAddress( ulIpAddress, (CHAR* )pszCallerID, TRUE ); pLci->ulCallerIDSize = (StrLenW( pszCallerID ) + 1) * sizeof(WCHAR); pLci->ulCallerIDFlags = LINECALLPARTYID_ADDRESS;
pLcp->ulPhysicalChannelId = 0xFFFFFFFF;
// Fill in shortcut outputs.
//
*ppTiParams = pTip; *ppTcInfo = pLci; *ppLcParams = pLcp; }
VOID CallCleanUp( IN VCCB* pVc )
// De-associates the VC from the tunnel, preparing for and de-activating
// the call.
//
{ NDIS_STATUS status; ULONG ulFlags;
ulFlags = ReadFlags( &pVc->ulFlags );
TRACE( TL_I, TM_Cm, ( "CallCleanUp(pV=$%p,cid=%d,act=%d)", pVc, (ULONG )pVc->usCallId, !!(ulFlags & VCBF_VcActivated) ) ); ASSERT( pVc->ulTag == MTAG_VCCB );
if (ReadFlags( &pVc->ulFlags ) & VCBF_VcActivated) { TRACE( TL_I, TM_Cm, ( "NdisMCmDeactVc" ) ); status = NdisMCmDeactivateVc( pVc->NdisVcHandle ); TRACE( TL_I, TM_Cm, ( "NdisMCmDeactVc=$%x", status ) ); ASSERT( status == NDIS_STATUS_SUCCESS );
ClearFlags( &pVc->ulFlags, VCBF_VcActivated ); DereferenceCall( pVc );
// The above actions lead to the call reference eventually going to 0,
// at which time clean up resumes in DereferenceCall.
//
} else { InactiveCallCleanUp( pVc ); } }
VOID CallSetupComplete( IN VCCB* pVc )
// Clean up 'pVc' allocations used only at call setup, if any.
//
{ if (InterlockedExchangePointer( &pVc->pMakeCall, NULL )) { ASSERT( pVc->pTmParams ); ASSERT( pVc->pTcParams ); ASSERT( pVc->pLcParams );
if (ReadFlags( &pVc->ulFlags ) & VCBF_DefaultLcParams) { // Caller did not provide any LcParams. Free the 'default' version we
// created for convenience.
//
FREE_NONPAGED( pVc->pLcParams ); }
pVc->pTmParams = NULL; pVc->pTcParams = NULL; pVc->pLcParams = NULL; }
UnlockIcs( pVc, FALSE ); }
VOID CloseCall( IN TUNNELWORK* pWork, IN TUNNELCB* pTunnel, IN VCCB* pVc, IN ULONG_PTR* punpArgs )
// A PTUNNELWORK routine to close the call on 'pVc'.
//
// This routine is called only at PASSIVE IRQL.
//
{ BOOLEAN fCompleteVcs;
TRACE( TL_I, TM_Fsm, ( "CloseCall(pV=$%p)", pVc ) );
// No context information so just free the work item.
//
FREE_TUNNELWORK( pTunnel->pAdapter, pWork );
// Close down the call.
//
NdisAcquireSpinLock( &pTunnel->lockT ); { NdisAcquireSpinLock( &pVc->lockV ); { fCompleteVcs = CloseCall2( pTunnel, pVc, TRESULT_Shutdown, GERR_None ); } NdisReleaseSpinLock( &pVc->lockV );
if (fCompleteVcs) { CompleteVcs( pTunnel ); } } NdisReleaseSpinLock( &pTunnel->lockT ); }
BOOLEAN CloseCall2( IN TUNNELCB* pTunnel, IN VCCB* pVc, IN USHORT usResult, IN USHORT usError )
// Close the call on VC 'pVc' of tunnel 'pTunnel'. 'UsResult' and
// 'usError' are the TRESULT_* and GERR_* codes to be reported in the
// StopCCN message, if applicable.
//
// Returns true if caller should call CompleteVcs after releasing 'lockV'
// or false if not.
//
// IMPORTANT: Caller must hold 'lockT' and 'lockV'.
//
{ ULONG ulFlags;
// Check if another path has completed the VC already. If so, there's no
// reason to continue. Without the local tunnel cancel optimization
// below, this check can be removed entirely and everything safely falls
// through. This check should include all "non-completing" conditions in
// CallTransitionComplete.
//
ulFlags = ReadFlags( &pVc->ulFlags ); if (!(ulFlags & VCBM_Pending)) { if (!(ulFlags & VCBF_CallClosableByPeer)) { TRACE( TL_A, TM_Cm, ( "Not closable" ) ); WPLOG( LL_A, LM_Cm, ( "pVc = %p not closable!", pVc ) ); return FALSE; } }
// For locally initiated tunnels, check if this VC is the only one on the
// tunnel, and if so, close the tunnel directly which slams this call.
// Without this, the call closure would still bring down the tunnel.
// However, the tunnel would complete it's transition normally, then be
// dropped. This speeds things up a little, giving quick response in the
// case where user cancels an attempt to connect to a wrong address or
// non-responsive server.
//
if (!(ReadFlags( &pTunnel->ulFlags) & TCBF_PeerInitiated)) { BOOLEAN fMultipleVcs;
NdisAcquireSpinLock( &pTunnel->lockVcs ); { fMultipleVcs = (pTunnel->listVcs.Flink != pTunnel->listVcs.Blink); } NdisReleaseSpinLock( &pTunnel->lockVcs );
if (!fMultipleVcs) { ScheduleTunnelWork( pTunnel, NULL, FsmCloseTunnel, (ULONG_PTR )usResult, (ULONG_PTR )usError, 0, 0, FALSE, FALSE ); return FALSE; } }
// Slam the call closed.
//
CallTransitionComplete( pTunnel, pVc, CS_Idle ); return TRUE; }
VOID CloseTunnel( IN TUNNELWORK* pWork, IN TUNNELCB* pTunnel, IN VCCB* pVc, IN ULONG_PTR* punpArgs )
// A PTUNNELWORK routine to slam closed tunnel 'pTunnel'. See also
// FsmCloseTunnel, which is often more appropriate.
//
// This routine is called only at PASSIVE IRQL.
//
{ TRACE( TL_I, TM_Fsm, ( "CloseTunnel(pT=$%p)", pTunnel ) );
// No context information so just free the work item.
//
FREE_TUNNELWORK( pTunnel->pAdapter, pWork );
// Close down the tunnel.
//
NdisAcquireSpinLock( &pTunnel->lockT ); { CloseTunnel2( pTunnel ); } NdisReleaseSpinLock( &pTunnel->lockT ); }
VOID CloseTunnel2( IN TUNNELCB* pTunnel )
// Close the tunnel 'pTunnel'.
//
// IMPORTANT: Caller must hold 'lockT'.
//
{ SetFlags( &pTunnel->ulFlags, TCBF_Closing ); TunnelTransitionComplete( pTunnel, CCS_Idle ); CompleteVcs( pTunnel ); }
VOID CompleteVcs( IN TUNNELCB* pTunnel )
// Complete the pending operation for each of the VCs on the completing
// list of tunnel 'pTunnel'.
//
// IMPORTANT: Caller must hold 'lockT'. This routine may release and
// re-acquire 'lockT'.
//
{ while (!IsListEmpty( &pTunnel->listCompletingVcs )) { LIST_ENTRY* pLink; VCCB* pVc; NDIS_STATUS status; LINKSTATUSINFO info; ULONG ulFlags; NDIS_STATUS statusVc;
if (pTunnel->listCompletingVcs.Flink->Flink == pTunnel->listCompletingVcs.Flink) { // This is a hack to work around a rare listCompletingVcs
// corruption problem whose cause has me baffled. When the
// problem occurs, a VCCB with it's link initialized appears in
// the list. This code removes the corrupted case hopefully
// resulting in exactly the same state as the normal path in the
// "else" clause.
//
pLink = pTunnel->listCompletingVcs.Flink; InitializeListHead( &pTunnel->listCompletingVcs ); ASSERT( FALSE ); ++g_ulCompletingVcCorruption; } else { // Pop the next completing VC from the list.
//
pLink = RemoveHeadList( &pTunnel->listCompletingVcs ); }
InitializeListHead( pLink );
// Take a reference covering use of the VC pointer obtained from the
// completing list.
//
pVc = CONTAINING_RECORD( pLink, VCCB, linkCompletingVcs ); ReferenceVc( pVc ); ASSERT(ReadFlags(&pVc->ulFlags) & VCBF_CompPending); ClearFlags( &pVc->ulFlags, VCBF_CompPending );
TRACE( TL_V, TM_Cm, ( "CompleteVc $%p", pVc ) );
NdisAcquireSpinLock( &pVc->lockV ); { // Note the pending flags then clear them, to ensure that all
// pending operations are completed exactly once. This is
// necessary since ClientOpen and ClientClose events may be
// pending simultaneously. (Thanks a lot NDIS guys).
//
ulFlags = ReadFlags( &pVc->ulFlags ); ClearFlags( &pVc->ulFlags, VCBM_Pending );
// Convert client close pending to client close completion,
// for reference later when call references reach zero. The
// flag determines if NdisMCmCloseCallComplete must be called.
//
if (ulFlags & VCBF_ClientClosePending) { SetFlags( &pVc->ulFlags, VCBF_ClientCloseCompletion ); }
// Before releasing the lock, make "safe" copies of any VC
// parameters we might need.
//
TransferLinkStatusInfo( pVc, &info ); statusVc = pVc->status; } NdisReleaseSpinLock( &pVc->lockV );
NdisReleaseSpinLock( &pTunnel->lockT ); { if (ulFlags & VCBF_PeerOpenPending) { TRACE( TL_N, TM_Cm, ( "PeerOpen complete, s=$%x", statusVc ) );
if (statusVc == NDIS_STATUS_SUCCESS) { // Peer initiated call succeeded.
//
ASSERT( ulFlags & VCBF_VcDispatched ); TRACE( TL_I, TM_Cm, ( "NdisMCmDispCallConn" ) ); NdisMCmDispatchCallConnected( pVc->NdisVcHandle ); TRACE( TL_I, TM_Cm, ( "NdisMCmDispCallConn done" ) );
IndicateLinkStatus( pVc, &info ); CallSetupComplete( pVc ); } else { // Peer initiated call failed.
//
if (ulFlags & VCBF_VcDispatched) { SetFlags( &pVc->ulFlags, VCBF_WaitCloseCall ); TRACE( TL_A, TM_Cm, ( "Call NdisMCmDispInCloseCall(s=$%x)", statusVc ) ); NdisMCmDispatchIncomingCloseCall( statusVc, pVc->NdisVcHandle, NULL, 0 ); TRACE( TL_I, TM_Cm, ( "NdisMCmDispInCloseCall done" ) );
// Client will call NdisClCloseCall which will get
// our LcmCloseCall handler called to clean up
// call setup, de-activate and delete the VC, as
// necessary.
//
} else { // Return the VC to "just created" state.
//
CallCleanUp( pVc ); } } } else if (ulFlags & VCBF_ClientOpenPending) { TRACE( TL_N, TM_Cm, ( "ClientOpen complete, s=$%x", statusVc ) );
if (statusVc == NDIS_STATUS_SUCCESS) { // Client initiated open, i.e. MakeCall, succeeded.
//
// Activating the VC is a CoNDIS preliminary to reporting
// the MakeCall complete. For L2TP, all it does is get
// the NDIS state flags set correctly.
//
TRACE( TL_I, TM_Cm, ( "Call NdisMCmActivateVc" ) ); ASSERT( pVc->pMakeCall ); status = NdisMCmActivateVc( pVc->NdisVcHandle, pVc->pMakeCall ); TRACE( TL_I, TM_Cm, ( "NdisMCmActivateVc=$%x", status ) ); ASSERT( status == NDIS_STATUS_SUCCESS );
{ BOOLEAN fCallActive;
SetFlags( &pVc->ulFlags, VCBF_VcActivated ); fCallActive = ReferenceCall( pVc ); ASSERT( fCallActive ); } }
// Update the call parameters
pVc->pMakeCall->CallMgrParameters->Transmit.PeakBandwidth = pVc->pMakeCall->CallMgrParameters->Transmit.TokenRate = pVc->pMakeCall->CallMgrParameters->Receive.PeakBandwidth = pVc->pMakeCall->CallMgrParameters->Receive.TokenRate = pVc->ulConnectBps / 8;
TRACE( TL_I, TM_Cm, ( "Call NdisMCmMakeCallComp(s=$%x)", statusVc ) ); ASSERT( pVc->pMakeCall ); NdisMCmMakeCallComplete( statusVc, pVc->NdisVcHandle, NULL, NULL, pVc->pMakeCall ); TRACE( TL_I, TM_Cm, ( "NdisMCmMakeCallComp done" ) );
if (statusVc == NDIS_STATUS_SUCCESS) { IndicateLinkStatus( pVc, &info ); CallSetupComplete( pVc ); } else { // Return the VC to "just created" state.
//
InactiveCallCleanUp( pVc ); } } else if (ulFlags & VCBF_PeerClosePending ) { TRACE( TL_N, TM_Cm, ( "PeerClose complete, s=$%x", statusVc ) );
// Peer initiated close completed.
//
SetFlags( &pVc->ulFlags, VCBF_WaitCloseCall ); TRACE( TL_I, TM_Cm, ( "Call NdisMCmDispInCloseCall(s=$%x)", statusVc ) ); NdisMCmDispatchIncomingCloseCall( statusVc, pVc->NdisVcHandle, NULL, 0 ); TRACE( TL_I, TM_Cm, ( "NdisMCmDispInCloseCall done" ) );
// Client will call NdisClCloseCall while processing the above
// which will get our LcmCloseCall handler called to
// de-activate and delete the VC, as necessary.
//
} else if (ulFlags & VCBF_ClientClosePending) { // This section eventually runs for all successful unclosed
// calls, whether peer or client initiated or closed.
//
TRACE( TL_N, TM_Cm, ( "ClientClose complete" ) );
// Deactivate the VC and return all sent packets to the client
// above. These events will eventually lead to the call being
// dereferenced to zero, at which time the close is completed,
// and if peer initiated, the VC is deleted.
//
// Note: When MakeCall is cancelled by a Close request, these
// actions occur during the InactiveCallCleanUp in the
// ClientOpenPending completion code handling, rather
// than the CallCleanUp (which leads to
// InactiveCallCleanUp) here. In this case, this block
// does NOT run even though the ClientClosePending flag
// is set. Consider this before adding code here.
//
CallCleanUp( pVc ); } } NdisAcquireSpinLock( &pTunnel->lockT );
// Remove the reference for use of the VC pointer from the completing
// list.
//
DereferenceVc( pVc ); } }
TUNNELCB* CreateTunnelCb( IN ADAPTERCB* pAdapter )
// Allocates and initializes a tunnel control block from the pool
// associated with 'pAdapter'. Tunnels are created unreferenced.
//
// Returns the allocated control block or NULL if allocation failed. The
// allocated block must eventually be freed with FREE_TUNNELCB, typically
// via DereferenceTunnel.
//
// IMPORTANT: Caller must hold the 'pAdapter->lockTunnels'.
//
{ TUNNELCB* pTunnel;
pTunnel = ALLOC_TUNNELCB( pAdapter ); if (pTunnel) { NdisZeroMemory( pTunnel, sizeof(*pTunnel ) );
InitializeListHead( &pTunnel->linkTunnels ); InitializeListHead( &pTunnel->listRequestingVcs ); InitializeListHead( &pTunnel->listCompletingVcs ); InitializeListHead( &pTunnel->listSendsOut ); InitializeListHead( &pTunnel->listOutOfOrder ); InitializeListHead( &pTunnel->listVcs ); InitializeListHead( &pTunnel->listWork );
NdisAllocateSpinLock( &pTunnel->lockT ); NdisAllocateSpinLock( &pTunnel->lockWork );
pTunnel->ulTag = MTAG_TUNNELCB; pTunnel->state = CCS_Idle;
// Choose the next non-zero sequential tunnel identifier.
//
pTunnel->usTunnelId = GetNextTunnelId( pAdapter );
// Default send window, "slow started". This is typically adjusted
// based on peer's Receive Window AVP when the tunnel is created, but
// if he doesn't include one this default is used.
//
pTunnel->ulSendWindow = pAdapter->info.MaxSendWindow >> 1; if (pTunnel->ulSendWindow == 0) { pTunnel->ulSendWindow = 1; }
// Initialize the estimated round trip time and send timeout per the
// suggestions in the draft/RFC.
//
pTunnel->ulRoundTripMs = pAdapter->ulInitialSendTimeoutMs; pTunnel->ulSendTimeoutMs = pTunnel->ulRoundTripMs;
pTunnel->ulMediaSpeed = L2TP_LanBps;
pTunnel->pTimerQ = ALLOC_TIMERQ( pAdapter ); if (!pTunnel->pTimerQ) { WPLOG( LL_A, LM_Res, ( "Failed to allocate TIMERQ") ); pTunnel->ulTag = MTAG_FREED; FREE_TUNNELCB( pAdapter, pTunnel ); return NULL; }
TimerQInitialize( pTunnel->pTimerQ ); ++pAdapter->ulTimers;
if (pAdapter->pszPassword) { UNALIGNED ULONG* pul;
// Password specified so peer should be authenticated. Choose a
// random challenge to send to peer.
//
pul = (UNALIGNED ULONG* )(pTunnel->achChallengeToSend); NdisGetCurrentSystemTime( (LARGE_INTEGER* )pul ); pul[ 1 ] = PtrToUlong( pAdapter ); pul[ 2 ] = PtrToUlong( pTunnel ); pul[ 3 ] = PtrToUlong( &pul ); }
ReferenceAdapter( pAdapter ); pTunnel->pAdapter = pAdapter; TRACE( TL_I, TM_Cm, ( "New TCB =$%p", pTunnel ) ); WPLOG( LL_I, LM_Cm, ( "New TCB =$%p", pTunnel ) ); }
return pTunnel; }
VOID DereferenceAf( IN ADAPTERCB* pAdapter )
// Removes a reference from the address family of adapter control block
// 'pAdapter', and when frees the block when the last reference is
// removed.
//
{ LONG lRef;
lRef = NdisInterlockedDecrement( &pAdapter->lAfRef );
TRACE( TL_N, TM_Ref, ( "DerefAf to %d", lRef ) ); ASSERT( lRef >= 0 );
if (lRef == 0) { // Tell NDIS it's close is complete.
//
TRACE( TL_I, TM_Cm, ( "NdisMCmCloseAfComp" ) ); NdisMCmCloseAddressFamilyComplete( NDIS_STATUS_SUCCESS, pAdapter->NdisAfHandle ); TRACE( TL_I, TM_Cm, ( "NdisMCmCloseAfComp done" ) );
// Remove the reference for the NdisAfHandle.
//
InterlockedExchangePointer( &pAdapter->NdisAfHandle, NULL ); DereferenceAdapter( pAdapter ); } }
VOID DereferenceCall( IN VCCB* pVc )
// Removes a reference from the call active on 'pVc', invoking call clean
// up when the value reaches zero.
//
{ LONG lRef; NDIS_STATUS status; ADAPTERCB* pAdapter; LIST_ENTRY* pLink;
pAdapter = pVc->pAdapter;
NdisAcquireSpinLock( &pVc->lockCall ); { lRef = --pVc->lCallRef; TRACE( TL_N, TM_Ref, ( "DerefC to %d", pVc->lCallRef ) ); } NdisReleaseSpinLock( &pVc->lockCall );
if (lRef == 0) { InactiveCallCleanUp( pVc ); } }
VOID DereferenceSap( IN ADAPTERCB* pAdapter )
// Removes a reference from the SAP active on 'pAdapter', invoking
// Deregiter SAP completion handling when the value reaches zero.
//
{ LONG lRef; NDIS_STATUS status;
NdisAcquireSpinLock( &pAdapter->lockSap ); { lRef = --pAdapter->lSapRef; TRACE( TL_N, TM_Ref, ( "DerefSap to %d", pAdapter->lSapRef ) ); } NdisReleaseSpinLock( &pAdapter->lockSap );
if (lRef == 0) { status = ScheduleWork( pAdapter, DeregisterSapPassive, pAdapter ); } }
LONG DereferenceTunnel( IN TUNNELCB* pTunnel )
// Dereference the tunnel control block 'pTunnel'. If no longer
// referenced, unlink, undo any TDIX reference, and free the tunnel
// control block.
//
// This routine will not try to acquire 'lockT' or any 'lockV'.
//
// Returns the reference count after the dereference.
//
{ ADAPTERCB* pAdapter; LIST_ENTRY* pLink; LONG lRef;
pAdapter = pTunnel->pAdapter;
NdisAcquireSpinLock( &pAdapter->lockTunnels ); { lRef = --(pTunnel->lRef); TRACE( TL_N, TM_Ref, ( "DerefTcb to %d", lRef ) ); ASSERT( lRef >= 0 );
if (lRef == 0) { if (!(ReadFlags( &pTunnel->ulFlags ) & (TCBF_PeerInitiated | TCBF_Closing))) { // We initiated this tunnel and all it's calls have terminated
// gracefully. Initiate a graceful tunnel closing exchange.
// We'll wind up back here with TCBF_Closing set.
//
ReferenceTunnel( pTunnel, TRUE ); ScheduleTunnelWork( pTunnel, NULL, FsmCloseTunnel, (ULONG_PTR )TRESULT_General, (ULONG_PTR )GERR_None, 0, 0, TRUE, FALSE ); } else if (pTunnel->linkTunnels.Flink != &pTunnel->linkTunnels) { // The graceful closing exchange has completed or none is
// indicated. Time to stop all activity on the tunnel.
//
// Remove the tunnel from the adapter's list of active
// tunnels. Initialize the list link so it won't be done
// again following the APCed TDIX clean up below. Since there
// are no VC references on the tunnel, no further receive path
// events will touch this control block.
//
RemoveEntryList( &pTunnel->linkTunnels ); InitializeListHead( &pTunnel->linkTunnels );
if (ReadFlags( &pTunnel->ulFlags ) & TCBF_HostRouteAdded) { // Undo the host route we added.
//
ReferenceTunnel( pTunnel, TRUE ); ScheduleTunnelWork( pTunnel, NULL, DeleteHostRoute, 0, 0, 0, 0, TRUE, FALSE ); }
if (ReadFlags( &pTunnel->ulFlags ) & TCBF_TdixReferenced) { // Undo our TDI extension context reference.
//
ReferenceTunnel( pTunnel, TRUE ); ScheduleTunnelWork( pTunnel, NULL, CloseTdix, 0, 0, 0, 0, TRUE, FALSE ); } }
lRef = pTunnel->lRef; } } NdisReleaseSpinLock( &pAdapter->lockTunnels );
if (lRef > 0) { return lRef; }
TRACE( TL_N, TM_Res, ( "Freeing TCB..." ) );
// Stop the timer queue, which causes a TE_Terminate event for any timers
// still running.
//
TimerQTerminate( pTunnel->pTimerQ, TunnelTqTerminateComplete, pAdapter );
// No references and all PASSIVE IRQL termination completed. Finish
// cleaning up the tunnel control block.
//
ASSERT( !pTunnel->pTqiHello ); ASSERT( IsListEmpty( &pTunnel->listVcs ) ); ASSERT( IsListEmpty( &pTunnel->listRequestingVcs ) ); ASSERT( IsListEmpty( &pTunnel->listCompletingVcs ) ); ASSERT( IsListEmpty( &pTunnel->listWork ) ); ASSERT( IsListEmpty( &pTunnel->listSendsOut ) ); ASSERT( IsListEmpty( &pTunnel->listOutOfOrder ) );
WPLOG( LL_M, LM_Res, ( "Free TUNNEL %p to %!IPADDR!, Tid %d, Peer's Tid %d ", pTunnel, pTunnel->address.ulIpAddress, pTunnel->usTunnelId, pTunnel->usAssignedTunnelId ) ); // Free the tunnel control block.
//
pTunnel->ulTag = MTAG_FREED; FREE_TUNNELCB( pAdapter, pTunnel );
TRACE( TL_I, TM_Res, ( "TCB freed $%p", pTunnel ) ); DereferenceAdapter( pAdapter ); return 0; }
VOID DereferenceVc( IN VCCB* pVc )
// Removes a reference to the VC control block 'pVc', and when frees the
// block when the last reference is removed.
//
{ LONG lRef;
lRef = NdisInterlockedDecrement( &pVc->lRef );
TRACE( TL_N, TM_Ref, ( "DerefV to %d", lRef ) ); ASSERT( lRef >= 0 );
if (lRef == 0) { ADAPTERCB* pAdapter;
pAdapter = pVc->pAdapter;
// Can make these assumptions because NDIS will not call the delete-VC
// handler while the VC is active. All the nasty VC clean up occurs
// before the VC is deactivated and the call closed.
//
ASSERT( IsListEmpty( &pVc->listSendsOut ) ); ASSERT( IsListEmpty( &pVc->listOutOfOrder ) ); ASSERT( !pVc->pTqiDelayedAck );
ASSERT( pVc->ulTag == MTAG_VCCB ); pVc->ulTag = MTAG_FREED;
FREE_VCCB( pAdapter, pVc ); DereferenceAdapter( pAdapter ); TRACE( TL_I, TM_Mp, ( "VCB freed $%p", pVc ) ); } }
VOID InactiveCallCleanUp( IN VCCB* pVc )
// Cleans up a deactivated call. To clean up a call that might be active,
// use CallCleanUp instead. Returns the VC to "just created" state, in
// case client decides to make another call without deleting the VC.
//
{ NDIS_STATUS status; ULONG ulFlags; BOOLEAN fVcCreated; ADAPTERCB* pAdapter; TUNNELCB* pTunnel; BOOLEAN fForceGarbageCollect;
TRACE( TL_I, TM_Cm, ( "InactiveCallCleanUp(pV=$%p)", pVc ) );
pAdapter = pVc->pAdapter;
// Release any call parameter allocations and the call-ID slot, if any.
//
CallSetupComplete( pVc ); fForceGarbageCollect = ReleaseCallIdSlot( pVc );
// Disassociate the VC from the tunnel. It is possible the no tunnel is
// associated, though only if short of memory.
//
pTunnel = pVc->pTunnel; if (!pTunnel) { TRACE( TL_A, TM_Cm, ( "Inactive VC w/o tunnel" ) ); return; }
NdisAcquireSpinLock( &pTunnel->lockT ); { RemoveEntryList( &pVc->linkRequestingVcs ); InitializeListHead( &pVc->linkRequestingVcs );
NdisAcquireSpinLock( &pTunnel->lockVcs ); { pVc->pTunnel = NULL; RemoveEntryList( &pVc->linkVcs ); InitializeListHead( &pVc->linkVcs ); } NdisReleaseSpinLock( &pTunnel->lockVcs ); } NdisReleaseSpinLock( &pTunnel->lockT );
// Flush queues, timers, and statistics.
//
NdisAcquireSpinLock( &pVc->lockV ); { LIST_ENTRY* pLink;
ulFlags = ReadFlags( &pVc->ulFlags ); ASSERT( !(ulFlags & VCBF_VcActivated) );
// Terminate any delayed acknowledge timer.
//
if (pVc->pTqiDelayedAck) { TimerQTerminateItem( pTunnel->pTimerQ, pVc->pTqiDelayedAck ); pVc->pTqiDelayedAck = NULL; }
// Flush any payloads from the "out" list.
//
while (!IsListEmpty( &pVc->listSendsOut )) { PAYLOADSENT* pPs;
pLink = RemoveHeadList( &pVc->listSendsOut ); InitializeListHead( pLink ); pPs = CONTAINING_RECORD( pLink, PAYLOADSENT, linkSendsOut );
TRACE( TL_I, TM_Cm, ( "Flush pPs=$%p", pPs ) );
// Terminate the timer. Doesn't matter if the terminate fails as
// the expire handler will fail to get a call reference and do
// nothing.
//
ASSERT( pPs->pTqiSendTimeout ); TimerQTerminateItem( pTunnel->pTimerQ, pPs->pTqiSendTimeout );
// Remove the context reference for linkage in the "out" queue.
//
pPs->status = NDIS_STATUS_FAILURE; DereferencePayloadSent( pPs ); }
// Discard any out-of-order packets.
//
while (!IsListEmpty( &pVc->listOutOfOrder )) { PAYLOADRECEIVED* pPr;
pLink = RemoveHeadList( &pVc->listOutOfOrder ); InitializeListHead( pLink ); pPr = CONTAINING_RECORD( pLink, PAYLOADRECEIVED, linkOutOfOrder );
TRACE( TL_I, TM_Cm, ( "Flush pPr=$%p", pPr ) );
FreeBufferToPool( &pAdapter->poolFrameBuffers, pPr->pBuffer, TRUE ); FREE_PAYLOADRECEIVED( pAdapter, pPr ); }
// Update the global statistics by adding in the values tabulated for
// this call. Also prints the statistics in some trace modes.
//
UpdateGlobalCallStats( pVc ); } NdisReleaseSpinLock( &pVc->lockV );
// Dereference the tunnel. Careful, this makes 'pTunnel' invalid from
// this point forward.
//
DereferenceTunnel( pTunnel );
// Return the VC to "just created" state.
//
pVc->usAssignedCallId = 0; pVc->state = CS_Idle; ClearFlags( &pVc->ulFlags, 0xFFFFFFFF ); pVc->usResult = 0; pVc->usError = 0; pVc->status = NDIS_STATUS_SUCCESS; pVc->ulConnectBps = 0; pVc->usNs = 0; pVc->ulMaxSendWindow = 0; pVc->ulAcksSinceSendTimeout = 0; pVc->lDeviationMs = 0; pVc->usNr = 0; NdisZeroMemory( &pVc->stats, sizeof(pVc->stats) );
pVc->ulRoundTripMs = pAdapter->ulInitialSendTimeoutMs; pVc->ulSendTimeoutMs = pVc->ulRoundTripMs;
pVc->ulSendWindow = pAdapter->info.MaxSendWindow >> 1; if (pVc->ulSendWindow == 0) { pVc->ulSendWindow = 1; }
if (ulFlags & VCBF_ClientCloseCompletion) { TRACE( TL_I, TM_Cm, ( "NdisMCmCloseCallComp(OK)" ) ); NdisMCmCloseCallComplete( NDIS_STATUS_SUCCESS, pVc->NdisVcHandle, NULL ); TRACE( TL_I, TM_Cm, ( "NdisMCmCloseCallComp done" ) );
// Careful, if this was a client created VC, client may have deleted
// it, so 'pVc' must not be referenced hereafter in that case.
//
}
// When peer initiates the call, we create the VC and so delete it
// here. Otherwise, client created it and we leave it to him to
// delete it when he's ready.
//
if (ulFlags & VCBF_VcCreated) { NDIS_STATUS retStatus;
TRACE( TL_I, TM_Recv, ( "NdisMCmDelVc" ) ); retStatus = NdisMCmDeleteVc( pVc->NdisVcHandle ); TRACE( TL_I, TM_Recv, ( "NdisMCmDelVc=$%x", retStatus ) ); ASSERT( retStatus == NDIS_STATUS_SUCCESS ); LcmCmDeleteVc( pVc );
// Careful, 'pVc' has been deleted and must not be referenced
// hereafter.
//
}
// Create garbage collection events on all the pools if it was determined
// above to be an appropriate time to do so, i.e. we just deactivated the
// last active VC.
//
if (fForceGarbageCollect) { CollectBufferPoolGarbage( &pAdapter->poolFrameBuffers ); CollectBufferPoolGarbage( &pAdapter->poolHeaderBuffers ); CollectPacketPoolGarbage( &pAdapter->poolPackets ); } }
VOID LockIcs( IN VCCB* pVc, IN BOOLEAN fGrace )
// Lock the 'pVc->pInCallSetup' pointer. If 'fGrace' is set, the "grace
// period" reference is locked, and if not the "alloc" reference is
// locked. See also UnlockIcs.
//
{ SetFlags( &pVc->ulFlags, (fGrace) ? VCBF_IcsGrace : VCBF_IcsAlloc ); }
NDIS_STATUS QueryCmInformation( IN ADAPTERCB* pAdapter, IN VCCB* pVc, IN NDIS_OID Oid, IN PVOID InformationBuffer, IN ULONG InformationBufferLength, OUT PULONG BytesWritten, OUT PULONG BytesNeeded )
// Handle Call Manager QueryInformation requests. Arguments are as for
// the standard NDIS 'MiniportQueryInformation' handler except this
// routine does not count on being serialized with respect to other
// requests.
//
{ #define L2TP_MaxLineName 64
typedef struct L2TP_CO_TAPI_LINE_CAPS { CO_TAPI_LINE_CAPS caps; WCHAR achLineName[ L2TP_MaxLineName + 1 ]; } L2TP_CO_TAPI_LINE_CAPS;
NDIS_STATUS status; ULONG ulInfo; VOID* pInfo; ULONG ulInfoLen; ULONG extension; ULONG ulPortIndex; CO_TAPI_CM_CAPS cmcaps; L2TP_CO_TAPI_LINE_CAPS l2tpcaps; CO_TAPI_ADDRESS_CAPS addrcaps; CO_TAPI_CALL_DIAGNOSTICS diags;
status = NDIS_STATUS_SUCCESS;
// The cases in this switch statement find or create a buffer containing
// the requested information and point 'pInfo' at it, noting it's length
// in 'ulInfoLen'. Since many of the OIDs return a ULONG, a 'ulInfo'
// buffer is set up as the default.
//
ulInfo = 0; pInfo = &ulInfo; ulInfoLen = sizeof(ulInfo);
switch (Oid) { case OID_CO_TAPI_CM_CAPS: { TRACE( TL_N, TM_Cm, ( "QCm(OID_CO_TAPI_CM_CAPS)" ) );
NdisZeroMemory( &cmcaps, sizeof(cmcaps) );
// The LINE and ADDRESS CAPS OIDs will be requested after this
// one.
//
cmcaps.ulCoTapiVersion = CO_TAPI_VERSION; cmcaps.ulNumLines = 1; // caps.ulFlags = 0;
pInfo = &cmcaps; ulInfoLen = sizeof(cmcaps); break; }
case OID_CO_TAPI_LINE_CAPS: { ULONG ulLineNameLen; WCHAR* pszLineName; CO_TAPI_LINE_CAPS* pInCaps; LINE_DEV_CAPS* pldc;
TRACE( TL_N, TM_Cm, ( "QCm(OID_CO_TAPI_LINE_CAPS)" ) );
if (InformationBufferLength < sizeof(L2TP_CO_TAPI_LINE_CAPS)) { status = NDIS_STATUS_INVALID_DATA; ulInfoLen = 0; break; }
ASSERT( InformationBuffer ); pInCaps = (CO_TAPI_LINE_CAPS* )InformationBuffer;
NdisZeroMemory( &l2tpcaps, sizeof(l2tpcaps) ); pldc = &l2tpcaps.caps.LineDevCaps;
l2tpcaps.caps.ulLineID = pInCaps->ulLineID;
pldc->ulTotalSize = pInCaps->LineDevCaps.ulTotalSize; pldc->ulNeededSize = (ULONG )((CHAR* )(&l2tpcaps + 1) - (CHAR* )(&l2tpcaps.caps.LineDevCaps)); pldc->ulUsedSize = pldc->ulNeededSize;
// pldc->ulProviderInfoSize = 0;
// pldc->ulProviderInfoOffset = 0;
// pldc->ulSwitchInfoSize = 0;
// pldc->ulSwitchInfoOffset = 0;
pldc->ulPermanentLineID = l2tpcaps.caps.ulLineID;
// Pass the DriverDesc from the registry as the line name. TAPI
// requires that this be a localizable string.
//
if (pAdapter->pszDriverDesc) { pszLineName = pAdapter->pszDriverDesc; } else { pszLineName = L"L2TP"; }
ulLineNameLen = StrLenW( pszLineName ) + 1; if (ulLineNameLen > L2TP_MaxLineName) { ulLineNameLen = L2TP_MaxLineName; }
NdisMoveMemory( l2tpcaps.achLineName, pszLineName, ulLineNameLen * sizeof(WCHAR) ); l2tpcaps.achLineName[ ulLineNameLen ] = L'\0'; pldc->ulLineNameSize = ulLineNameLen * sizeof(WCHAR); pldc->ulLineNameOffset = (ULONG ) ((CHAR* )l2tpcaps.achLineName - (CHAR* )pldc); pldc->ulStringFormat = STRINGFORMAT_UNICODE;
// pldc->ulAddressModes = 0;
pldc->ulNumAddresses = 1; pldc->ulBearerModes = LINEBEARERMODE_DATA; pldc->ulMaxRate = L2TP_LanBps; pldc->ulMediaModes = LINEMEDIAMODE_UNKNOWN | LINEMEDIAMODE_DIGITALDATA;
// pldc->ulGenerateToneModes = 0;
// pldc->ulGenerateToneMaxNumFreq = 0;
// pldc->ulGenerateDigitModes = 0;
// pldc->ulMonitorToneMaxNumFreq = 0;
// pldc->ulMonitorToneMaxNumEntries = 0;
// pldc->ulMonitorDigitModes = 0;
// pldc->ulGatherDigitsMinTimeout = 0;
// pldc->ulGatherDigitsMaxTimeout = 0;
// pldc->ulMedCtlDigitMaxListSize = 0;
// pldc->ulMedCtlMediaMaxListSize = 0;
// pldc->ulMedCtlToneMaxListSize = 0;
// pldc->ulMedCtlCallStateMaxListSize = 0;
// pldc->ulDevCapFlags = 0;
pldc->ulMaxNumActiveCalls = 1;
// pldc->ulAnswerMode = 0;
// pldc->ulRingModes = 0;
// pldc->ulLineStates = 0;
// pldc->ulUUIAcceptSize = 0;
// pldc->ulUUIAnswerSize = 0;
// pldc->ulUUIMakeCallSize = 0;
// pldc->ulUUIDropSize = 0;
// pldc->ulUUISendUserUserInfoSize = 0;
// pldc->ulUUICallInfoSize = 0;
// pldc->MinDialParams = 0;
// pldc->MaxDialParams = 0;
// pldc->DefaultDialParams = 0;
// pldc->ulNumTerminals = 0;
// pldc->ulTerminalCapsSize = 0;
// pldc->ulTerminalCapsOffset = 0;
// pldc->ulTerminalTextEntrySize = 0;
// pldc->ulTerminalTextSize = 0;
// pldc->ulTerminalTextOffset = 0;
// pldc->ulDevSpecificSize = 0;
// pldc->ulDevSpecificOffset = 0;
// pldc->ulLineFeatures;
// pldc->ulSettableDevStatus;
// pldc->ulDeviceClassesSize;
// pldc->ulDeviceClassesOffset;
// pldc->PermanentLineGuid;
pldc->ulAddressTypes = LINEADDRESSTYPE_IPADDRESS;
// pldc->ProtocolGuid;
// pldc->ulAvailableTracking;
pInfo = &l2tpcaps; ulInfoLen = sizeof(l2tpcaps); break; }
case OID_CO_TAPI_ADDRESS_CAPS: { CO_TAPI_ADDRESS_CAPS* pInCaps; LINE_ADDRESS_CAPS* plac;
TRACE( TL_N, TM_Cm, ( "QCm(OID_CO_TAPI_ADDRESS_CAPS)" ) );
if (InformationBufferLength < sizeof(CO_TAPI_ADDRESS_CAPS)) { status = NDIS_STATUS_INVALID_DATA; ulInfoLen = 0; break; }
ASSERT( InformationBuffer ); pInCaps = (CO_TAPI_ADDRESS_CAPS* )InformationBuffer;
NdisZeroMemory( &addrcaps, sizeof(addrcaps) );
addrcaps.ulLineID = pInCaps->ulLineID; addrcaps.ulAddressID = pInCaps->ulAddressID;
plac = &addrcaps.LineAddressCaps;
plac->ulTotalSize = sizeof(LINE_ADDRESS_CAPS); plac->ulNeededSize = sizeof(LINE_ADDRESS_CAPS); plac->ulUsedSize = sizeof(LINE_ADDRESS_CAPS); plac->ulLineDeviceID = addrcaps.ulLineID; // plac->ulAddressSize = 0;
// plac->ulAddressOffset = 0;
// plac->ulDevSpecificSize = 0;
// plac->ulDevSpecificOffset = 0;
// plac->ulAddressSharing = 0;
// plac->ulAddressStates = 0;
// plac->ulCallInfoStates = 0;
// plac->ulCallerIDFlags = 0;
// plac->ulCalledIDFlags = 0;
// plac->ulConnectedIDFlags = 0;
// plac->ulRedirectionIDFlags = 0;
// plac->ulRedirectingIDFlags = 0;
// plac->ulCallStates = 0;
// plac->ulDialToneModes = 0;
// plac->ulBusyModes = 0;
// plac->ulSpecialInfo = 0;
// plac->ulDisconnectModes = 0;
plac->ulMaxNumActiveCalls = (ULONG )pAdapter->usMaxVcs;
// plac->ulMaxNumOnHoldCalls = 0;
// plac->ulMaxNumOnHoldPendingCalls = 0;
// plac->ulMaxNumConference = 0;
// plac->ulMaxNumTransConf = 0;
// plac->ulAddrCapFlags = 0;
// plac->ulCallFeatures = 0;
// plac->ulRemoveFromConfCaps = 0;
// plac->ulRemoveFromConfState = 0;
// plac->ulTransferModes = 0;
// plac->ulParkModes = 0;
// plac->ulForwardModes = 0;
// plac->ulMaxForwardEntries = 0;
// plac->ulMaxSpecificEntries = 0;
// plac->ulMinFwdNumRings = 0;
// plac->ulMaxFwdNumRings = 0;
// plac->ulMaxCallCompletions = 0;
// plac->ulCallCompletionConds = 0;
// plac->ulCallCompletionModes = 0;
// plac->ulNumCompletionMessages = 0;
// plac->ulCompletionMsgTextEntrySize = 0;
// plac->ulCompletionMsgTextSize = 0;
// plac->ulCompletionMsgTextOffset = 0;
pInfo = &addrcaps; ulInfoLen = sizeof(addrcaps); break; }
case OID_CO_TAPI_GET_CALL_DIAGNOSTICS: { TRACE( TL_N, TM_Cm, ( "QCm(OID_CO_TAPI_GET_CALL_DIAGS)" ) );
if (!pVc) { status = NDIS_STATUS_INVALID_DATA; ulInfoLen = 0; break; }
NdisZeroMemory( &diags, sizeof(diags) );
diags.ulOrigin = (ReadFlags( &pVc->ulFlags ) & VCBF_PeerInitiatedCall) ? LINECALLORIGIN_EXTERNAL : LINECALLORIGIN_OUTBOUND; diags.ulReason = LINECALLREASON_DIRECT;
pInfo = &diags; ulInfoLen = sizeof(diags); break; }
default: { TRACE( TL_A, TM_Cm, ( "QCm-OID=$%08x?", Oid ) ); status = NDIS_STATUS_NOT_SUPPORTED; ulInfoLen = 0; break; } }
if (ulInfoLen > InformationBufferLength) { // Caller's buffer is too small. Tell him what he needs.
//
*BytesNeeded = ulInfoLen; status = NDIS_STATUS_INVALID_LENGTH; } else { // Copy the found result to caller's buffer.
//
if (ulInfoLen > 0) { NdisMoveMemory( InformationBuffer, pInfo, ulInfoLen ); DUMPDW( TL_N, TM_Mp, pInfo, ulInfoLen ); }
*BytesNeeded = *BytesWritten = ulInfoLen; }
return status; }
VOID ReferenceAf( IN ADAPTERCB* pAdapter )
// Adds areference to the address family of adapter block, 'pAdapter'.
//
{ LONG lRef;
lRef = NdisInterlockedIncrement( &pAdapter->lAfRef );
TRACE( TL_N, TM_Ref, ( "RefAf to %d", lRef ) ); }
BOOLEAN ReferenceCall( IN VCCB* pVc )
// Returns true if a reference is added to the active call on VC control
// block, 'pVc', or false if no reference was added because no call is
// active.
//
{ BOOLEAN fActive;
NdisAcquireSpinLock( &pVc->lockCall ); { if (ReadFlags( &pVc->ulFlags ) & VCBF_VcActivated) { fActive = TRUE; ++pVc->lCallRef; TRACE( TL_N, TM_Ref, ( "RefC to %d", pVc->lCallRef ) ); } else { TRACE( TL_N, TM_Ref, ( "RefC denied" ) ); fActive = FALSE; } } NdisReleaseSpinLock( &pVc->lockCall );
return fActive; }
BOOLEAN ReferenceSap( IN ADAPTERCB* pAdapter )
// Returns true if a reference is added to the active SAP on adapter
// 'pAdapter', or false if no reference was added because no SAP is
// active.
//
{ BOOLEAN fActive;
NdisAcquireSpinLock( &pAdapter->lockSap ); { if (ReadFlags( &pAdapter->ulFlags ) & ACBF_SapActive) { fActive = TRUE; ++pAdapter->lSapRef; TRACE( TL_N, TM_Ref, ( "RefSap to %d", pAdapter->lSapRef ) ); } else { TRACE( TL_N, TM_Ref, ( "RefSap denied" ) ); fActive = FALSE; } } NdisReleaseSpinLock( &pAdapter->lockSap );
return fActive; }
LONG ReferenceTunnel( IN TUNNELCB* pTunnel, IN BOOLEAN fHaveLockTunnels )
// Reference the tunnel control block 'pTunnel'. 'FHaveLockTunnels' is
// set when the caller holds 'ADAPTERCB.lockTunnels'.
//
// Returns the reference count after the reference.
//
{ LONG lRef; ADAPTERCB* pAdapter;
if (!fHaveLockTunnels) { pAdapter = pTunnel->pAdapter; NdisAcquireSpinLock( &pAdapter->lockTunnels ); }
lRef = ++(pTunnel->lRef); TRACE( TL_N, TM_Ref, ( "RefT to %d", lRef ) );
if (!fHaveLockTunnels) { NdisReleaseSpinLock( &pAdapter->lockTunnels ); }
return lRef; }
VOID ReferenceVc( IN VCCB* pVc )
// Adds a reference to the VC control block 'pVc'.
//
{ LONG lRef;
lRef = NdisInterlockedIncrement( &pVc->lRef );
TRACE( TL_N, TM_Ref, ( "RefV to %d", lRef ) ); TRACE( TL_N, TM_Ref, ( "pVc = %p, RefV to %d", pVc, lRef ) ); }
BOOLEAN ReleaseCallIdSlot( IN VCCB* pVc )
// Releases 'pVc's reserved Call-ID slot in the adapter's VC table.
//
// Returns true if a release occurs and results in all slots being
// available, false otherwise.
//
{ ADAPTERCB* pAdapter; USHORT usCallId; BOOLEAN fAllSlotsAvailable;
pAdapter = pVc->pAdapter; usCallId = pVc->usCallId; pVc->usCallId = 0; fAllSlotsAvailable = FALSE;
if (usCallId > 0 && usCallId <= pAdapter->usMaxVcs) { NdisAcquireSpinLock( &pAdapter->lockVcs ); { pAdapter->ppVcs[ usCallId - 1 ] = NULL; ++(pAdapter->lAvailableVcSlots);
if (pAdapter->lAvailableVcSlots >= (LONG )pAdapter->usMaxVcs) { fAllSlotsAvailable = TRUE; } } NdisReleaseSpinLock( &pAdapter->lockVcs ); }
return fAllSlotsAvailable; }
NDIS_STATUS ReserveCallIdSlot( IN VCCB* pVc )
// Reserves a Call-ID slot for 'pVc' in the adapter's table.
//
// Returns NDIS_STATUS_SUCCESS if successful, or an error code.
//
{ NDIS_STATUS status; ADAPTERCB* pAdapter; VCCB** ppVc; USHORT i;
pAdapter = pVc->pAdapter;
NdisAcquireSpinLock( &pAdapter->lockVcs ); { // At this point, we have a VC for the received call request that's
// been successfully activated and dispatched to the client. Reserve
// a Call-ID in the adapter's look-up table.
//
if (pAdapter->lAvailableVcSlots > 0) { for (i = 0, ppVc = pAdapter->ppVcs; i < pAdapter->usMaxVcs; ++i, ++ppVc) { if (!*ppVc) { // The -1 reserves the ID. If/when the call negotiation
// completes successfully it will be changed to the
// address of the VCCB. Call-IDs are 1-based because L2TP
// reserves Call-ID 0 to mean the tunnel itself.
//
*ppVc = (VCCB* )-1; pVc->usCallId = i + 1; break; } }
ASSERT( i < pAdapter->usMaxVcs ); --(pAdapter->lAvailableVcSlots); status = NDIS_STATUS_SUCCESS; } else { // No Call-ID slots available. This means the client accepted the
// VC even though it put us over our configured limit. Something
// is mismatched in the configuration. Assign a Call-ID above the
// table size for use only in terminating the call gracefully.
//
TRACE( TL_N, TM_Misc, ( "No Call-ID slots?" ) ); pVc->usCallId = GetNextTerminationCallId( pAdapter ); status = NDIS_STATUS_NOT_ACCEPTED; } } NdisReleaseSpinLock( &pAdapter->lockVcs );
return status; }
TUNNELCB* SetupTunnel( IN ADAPTERCB* pAdapter, IN ULONG ulIpAddress, IN USHORT usUdpPort, IN USHORT usAssignedTunnelId, IN BOOLEAN fExclusive )
// Sets up a tunnel to remote peer with IP address 'ulIpAddress' and
// prepares it for sending or receiving messages. 'PAdapter' is the
// owning adapter control block. 'UlIpAddress' is the remote peer's IP
// address in network byte-order. 'UsAssignedTunnelId', if non-0,
// indicates the assigned Tunnel-ID that must match in addition to the IP
// address. If 'FExclusive' is clear an existing tunnel to the peer is
// acceptable. If set, a new tunnel is created even if a matching one
// already exists.
//
// Returns the address of the tunnel control block if successful, or NULL
// if not. If successful the block is already linked into the adapters
// list of active tunnels and referenced, i.e. DereferenceTunnel must be
// called during clean-up.
//
{ TUNNELCB* pTunnel;
TRACE( TL_V, TM_Misc, ( "SetupTunnel" ) );
NdisAcquireSpinLock( &pAdapter->lockTunnels ); { // If an existing tunnel would be acceptable, find the first existing
// tunnel with peer's IP address and, if non-0, assigned Tunnel-ID.
// Typically, none will be found and we go on to create a new one
// anyway.
//
pTunnel = (fExclusive) ? NULL : TunnelCbFromIpAddressAndAssignedTunnelId( pAdapter, ulIpAddress, usUdpPort, usAssignedTunnelId );
if (!pTunnel) { pTunnel = CreateTunnelCb( pAdapter ); if (!pTunnel) { WPLOG( LL_A, LM_Res, ( "Failed to allocate TCB!" ) ); NdisReleaseSpinLock( &pAdapter->lockTunnels ); return NULL; } WPLOG( LL_M, LM_Cm, ( "New TUNNEL %p to %!IPADDR!", pTunnel, ulIpAddress) );
// Associate peer's IP address with the tunnel.
//
pTunnel->address.ulIpAddress = ulIpAddress;
// Link the block into the adapter's list of active tunnels.
//
InsertHeadList( &pAdapter->listTunnels, &pTunnel->linkTunnels ); } else { TRACE( TL_A, TM_Cm, ( "Existing Tunnel %p", pTunnel ) ); WPLOG( LL_M, LM_Cm, ( "Existing TUNNEL %p to %!IPADDR!", pTunnel, ulIpAddress) ); }
// Reference the tunnel control block. Hereafter, clean-up must
// include a call to DereferenceTunnel.
//
ReferenceTunnel( pTunnel, TRUE ); } NdisReleaseSpinLock( &pAdapter->lockTunnels );
return pTunnel; }
VOID SetupVcAsynchronously( IN TUNNELCB* pTunnel, IN ULONG ulIpAddress, IN CHAR* pBuffer, IN CONTROLMSGINFO* pControl )
// Called by ReceiveControl to set up a VC for the incoming call described
// in 'pInfo', 'pControl', and 'pBuffer' using the necessary asynchronous
// CoNdis calls.
//
{ NDIS_STATUS status; ADAPTERCB* pAdapter; VCCB* pVc; INCALLSETUP* pIcs; NDIS_HANDLE NdisVcHandle; ULONG ulMask; BOOLEAN fTunnelClosing; BOOLEAN fCallActive;
TRACE( TL_V, TM_Misc, ( "SetupVcAsync" ) );
pAdapter = pTunnel->pAdapter;
// Call our own CreateVc handler directly to allocate and initialize the
// incoming call's VC.
//
status = LcmCmCreateVc( pAdapter, NULL, &pVc ); if (status != NDIS_STATUS_SUCCESS) { ScheduleTunnelWork( pTunnel, NULL, FsmCloseTunnel, (ULONG_PTR )TRESULT_GeneralWithError, (ULONG_PTR )GERR_NoResources, 0, 0, FALSE, FALSE ); FreeBufferToPool( &pAdapter->poolFrameBuffers, pBuffer, TRUE ); return; }
// Allocate an "incoming call setup" context and initialize it from the
// receive buffer information arguments.
//
pIcs = ALLOC_INCALLSETUP( pAdapter ); if (!pIcs) { WPLOG( LL_A, LM_Res, ( "Failed to allocate INCALLSETUP!" ) ); LcmCmDeleteVc( pVc ); ScheduleTunnelWork( pTunnel, NULL, FsmCloseTunnel, (ULONG_PTR )TRESULT_GeneralWithError, (ULONG_PTR )GERR_NoResources, 0, 0, FALSE, FALSE ); FreeBufferToPool( &pAdapter->poolFrameBuffers, pBuffer, TRUE ); return; }
pIcs->pBuffer = pBuffer; NdisMoveMemory( &pIcs->control, pControl, sizeof(pIcs->control) );
BuildCallParametersShell( pAdapter, ulIpAddress, sizeof(pIcs->achCallParams), pIcs->achCallParams, &pVc->pTiParams, &pVc->pTcInfo, &pVc->pLcParams );
LockIcs( pVc, FALSE ); pVc->pInCall = pIcs;
// Default is success with errors filled in if they occur.
//
pVc->usResult = 0; pVc->usError = GERR_None;
// Mark the call as initiated by the peer so we know which notifications
// to give when the result is known.
//
ulMask = (VCBF_PeerInitiatedCall | VCBF_PeerOpenPending); if (*(pControl->pusMsgType) == CMT_ICRQ) { ulMask |= VCBF_IncomingFsm; } SetFlags( &pVc->ulFlags, ulMask );
// Add a tunnel reference for this call on this VC, set the back pointer
// to the owning tunnel, and link the VC into the tunnel's list of
// associated VCs.
//
ReferenceTunnel( pTunnel, FALSE ); NdisAcquireSpinLock( &pTunnel->lockT ); { if (ReadFlags( &pTunnel->ulFlags ) & TCBF_Closing) { // This is unlikely because SetupTunnel only finds non-closing
// tunnels, but this check and linkage must occur atomically under
// 'lockT'. New VCs must not be linked onto closing tunnels.
//
fTunnelClosing = TRUE; } else { fTunnelClosing = FALSE; NdisAcquireSpinLock( &pTunnel->lockVcs ); { pVc->pTunnel = pTunnel; InsertTailList( &pTunnel->listVcs, &pVc->linkVcs ); } NdisReleaseSpinLock( &pTunnel->lockVcs ); } } NdisReleaseSpinLock( &pTunnel->lockT );
if (fTunnelClosing) { CallSetupComplete( pVc ); LcmCmDeleteVc( pVc ); FreeBufferToPool( &pAdapter->poolFrameBuffers, pBuffer, TRUE ); DereferenceTunnel( pTunnel ); return; }
// Peer MUST provide a Call-ID to pass back in the L2TP header of call
// control and payload packets.
//
if (!pControl->pusAssignedCallId || *(pControl->pusAssignedCallId) == 0) { TRACE( TL_A, TM_Misc, ( "No assigned CID?" ) ); WPLOG( LL_A, LM_Misc, ( "Failure - Peer doesn't provide a Call-ID!" ) ); pVc->usResult = CRESULT_GeneralWithError; pVc->usError = GERR_BadCallId; SetupVcComplete( pTunnel, pVc ); return; }
// Check if the request has a chance of succeeding before getting the
// client involved.
//
if (!(ReadFlags( &pVc->ulFlags ) & VCBF_IncomingFsm)) { // Fail requests to our LAC requiring asynchronous PPP framing or an
// analog or digital WAN connection. NDISWAN doesn't provide
// asynchronous PPP framing, and we don't currently support non-LAN
// WAN relays.
//
if (!pControl->pulFramingType || !(*(pControl->pulFramingType) & FBM_Sync)) { TRACE( TL_A, TM_Misc, ( "Not sync framing type?" ) ); WPLOG( LL_A, LM_Misc, ( "Failure - Not supported framing type!" ) );
if (!(pAdapter->ulFlags & ACBF_IgnoreFramingMismatch)) { pVc->usResult = CRESULT_NoFacilitiesPermanent; pVc->usError = GERR_None; SetupVcComplete( pTunnel, pVc ); return; } }
if (pControl->pulBearerType && *(pControl->pulBearerType) != 0) { TRACE( TL_A, TM_Misc, ( "Cannot do bearer type" ) ); WPLOG( LL_A, LM_Misc, ( "Failure - Not supported bearer type!" ) ); pVc->usResult = CRESULT_NoFacilitiesPermanent; pVc->usError = GERR_None; SetupVcComplete( pTunnel, pVc ); return; } }
// Tell NDIS to notify the client of the new VC and give us it's handle.
//
ASSERT( pAdapter->NdisAfHandle ); TRACE( TL_I, TM_Cm, ( "NdisMCmCreateVc" ) ); status = NdisMCmCreateVc( pAdapter->MiniportAdapterHandle, pAdapter->NdisAfHandle, pVc, &pVc->NdisVcHandle );
if (status != NDIS_STATUS_SUCCESS) { TRACE( TL_I, TM_Cm, ( "NdisMCmCreateVc=$%x,h=$%p", status, pVc->NdisVcHandle ) ); WPLOG( LL_A, LM_Cm, ( "NdisMCmCreateVc failed, pVc = %p, s= %08x!", pVc, status ) ); pVc->usResult = CRESULT_GeneralWithError; pVc->usError = GERR_NoResources; SetupVcComplete( pTunnel, pVc ); return; } SetFlags( &pVc->ulFlags, VCBF_VcCreated );
// Tell NDIS the VC is active.
//
TRACE( TL_I, TM_Cm, ( "NdisMCmActivateVc" ) ); status = NdisMCmActivateVc( pVc->NdisVcHandle, (PCO_CALL_PARAMETERS )pVc->pInCall->achCallParams );
if (status != NDIS_STATUS_SUCCESS ) { TRACE( TL_A, TM_Cm, ( "NdisMCmActivateVc=$%x", status ) ); WPLOG( LL_A, LM_Cm, ( "NdisMCmActivateVc failed, pVc = %p, s= %08x", pVc, status ) ); pVc->usResult = CRESULT_GeneralWithError; pVc->usError = GERR_NoResources; SetupVcComplete( pTunnel, pVc ); return; }
// Mark that the call is active, a state where both client and peer close
// requests should be accepted.
//
SetFlags( &pVc->ulFlags, (VCBF_VcActivated | VCBF_CallClosableByClient | VCBF_CallClosableByPeer) ); fCallActive = ReferenceCall( pVc ); ASSERT( fCallActive );
// Tell NDIS to tell the client about the call. The dispatched flag is
// set here rather in the completion because, according to JameelH, it is
// valid to call NdisMCmDispatchIncomingCloseCall even if client pends on
// the dispatch. A reference on the SAP must be held during the operation
// since it uses the NdisSapHandle. The reference is released as soon as
// the call returns. A VC reference is taken to prevent the VC from being
// deleted before the completion handler is called. The VC reference is
// removed by the completion handler.
//
if (!ReferenceSap( pAdapter )) { pVc->usResult = CRESULT_NoFacilitiesTemporary; pVc->usError = GERR_None; SetupVcComplete( pTunnel, pVc ); return; }
fCallActive = ReferenceCall( pVc ); ReferenceVc( pVc ); ASSERT( fCallActive ); SetFlags( &pVc->ulFlags, VCBF_WaitInCallComplete ); TRACE( TL_I, TM_Cm, ( "Call NdisMCmDispInCall" ) ); WPLOG( LL_M, LM_Cm, ( "Incoming CALL %p on TUNNEL %p", pVc, pTunnel) ); status = NdisMCmDispatchIncomingCall( pAdapter->NdisSapHandle, pVc->NdisVcHandle, (CO_CALL_PARAMETERS* )pVc->pInCall->achCallParams ); TRACE( TL_I, TM_Cm, ( "NdisMCmDispInCall=$%x", status ) );
DereferenceSap( pAdapter );
if (status == NDIS_STATUS_SUCCESS || status == NDIS_STATUS_PENDING) { SetFlags( &pVc->ulFlags, VCBF_VcDispatched ); }
if (status != NDIS_STATUS_PENDING) { LcmCmIncomingCallComplete( status, pVc, NULL ); }
// Next stop is our LcmCmIncomingCallComplete handler which will call
// SetupVcComplete with client's reported status.
//
}
VOID SetupVcComplete( IN TUNNELCB* pTunnel, IN VCCB* pVc )
// Called when the asynchronous incoming call VC setup result is known.
// 'PVc' is the non-NULL set up VC, with 'usResult' and 'usError' fields
// indicating the status thus far. 'PTunnel' is the associated tunnel.
//
{ NDIS_STATUS status; ADAPTERCB* pAdapter; BOOLEAN fCallerFreesBuffer; ULONG ulcpVcs; VCCB** ppVcs;
TRACE( TL_V, TM_Misc, ( "SetupVcComp,pVc=%p,cid=%d,r=%d,e=%d", pVc, (ULONG )pVc->usCallId, (ULONG )pVc->usResult, (ULONG )pVc->usError ) );
pAdapter = pVc->pAdapter;
// Lock up 'pInCall' because as soon as the call is activated the call can
// be torn down and 'pInCall' destroyed. See also comments in UnlockIcs.
//
LockIcs( pVc, TRUE ); { // OK, we're done trying to to set up the VC asynchronously. A VCCB
// and INCALLSETUP were successfully allocated, which is the minimum
// required to be graceful with peer. Reserve a Call-ID in the
// adapter's look-up table.
//
status = ReserveCallIdSlot( pVc ); if (status == NDIS_STATUS_SUCCESS) { ActivateCallIdSlot( pVc ); } else { pVc->usResult = CRESULT_Busy; pVc->usError = GERR_None; }
// Duplicate the tail of the receive path processing that would have
// occurred if we'd not been forced to go asynchronous.
//
NdisAcquireSpinLock( &pTunnel->lockT ); { fCallerFreesBuffer = ReceiveControlExpected( pTunnel, pVc, pVc->pInCall->pBuffer, &pVc->pInCall->control );
CompleteVcs( pTunnel ); } NdisReleaseSpinLock( &pTunnel->lockT );
if (fCallerFreesBuffer) { FreeBufferToPool( &pVc->pAdapter->poolFrameBuffers, pVc->pInCall->pBuffer, TRUE ); } DBG_else { ASSERT( FALSE ); } } UnlockIcs( pVc, TRUE ); }
VOID TunnelTqTerminateComplete( IN TIMERQ* pTimerQ, IN VOID* pContext )
// TIMERQTERMINATECOMPLETE handler for 'TUNNELCB.pTimerQ'.
//
{ ADAPTERCB* pAdapter;
pAdapter = (ADAPTERCB* )pContext; --pAdapter->ulTimers; FREE_TIMERQ( pAdapter, pTimerQ ); }
VOID UnlockIcs( IN VCCB* pVc, IN BOOLEAN fGrace )
// Unlock the 'pVc->pInCallSetup' pointer. If 'fGrace' is set, the "grace
// period" reference is unlocked, and if not the "alloc" reference is
// unlocked. If both references are gone, then do the actual cleanup.
//
// Note: Regular reference counts don't work well here because there are
// several possible causes of the "alloc" unlock and they are not
// necessarily mutually exclusive. However, we need to prevent the
// 'pInCall' pointer from being freed until the incoming call
// response has been sent out, which in turn requires knowledge of
// whether the "activate for receive" succeeded.
//
{ INCALLSETUP *pInCall = NULL; ADAPTERCB *pAdapter; ClearFlags( &pVc->ulFlags, (fGrace) ? VCBF_IcsGrace : VCBF_IcsAlloc );
if (!(ReadFlags( &pVc->ulFlags ) & (VCBF_IcsGrace | VCBF_IcsAlloc))) { NdisAcquireSpinLock(&pVc->lockV); if(pVc->pInCall) { pInCall = pVc->pInCall; pAdapter = pVc->pAdapter; pVc->pInCall = NULL; pVc->pTmParams = NULL; pVc->pTcParams = NULL; pVc->pLcParams = NULL; } NdisReleaseSpinLock(&pVc->lockV); if(pInCall != NULL) { FREE_INCALLSETUP( pAdapter, pInCall ); } } #if 0
if (!(ReadFlags( &pVc->ulFlags ) & (VCBF_IcsGrace | VCBF_IcsAlloc)) && pVc->pInCall) { FREE_INCALLSETUP( pAdapter, pInCall ); pVc->pInCall = NULL; pVc->pTmParams = NULL; pVc->pTcParams = NULL; pVc->pLcParams = NULL; }
#endif
}
|