/*++ Copyright (c) 1998-1999 Microsoft Corporation Module Name: rm.c Abstract: Implementation of the "Resource Manager" APIs. Revision History: Who When What -------- -------- ---------------------------------------------- josephj 11-18-98 Created Notes: --*/ #include // // File-specific debugging defaults. // #define TM_CURRENT TM_RM //========================================================================= // U T I L I T Y M A C R O S //========================================================================= #define RM_ALLOC(_pp, _size, _tag) \ NdisAllocateMemoryWithTag((_pp), (_size), (_tag)) #define RM_ALLOCSTRUCT(_p, _tag) \ NdisAllocateMemoryWithTag(&(_p), sizeof(*(_p)), (_tag)) #define RM_FREE(_p) NdisFreeMemory((_p), 0, 0) #define RM_ZEROSTRUCT(_p) \ NdisZeroMemory((_p), sizeof(*(_p))) #define RM_PRIVATE_UNLINK_NEXT_HASH(_pHashTable, _ppLink) \ ((*(_ppLink) = (*(_ppLink))->pNext), ((_pHashTable)->NumItems--)) #define SET_RM_STATE(_pHdr, _Mask, _Val) \ (((_pHdr)->RmState) = (((_pHdr)->RmState) & ~(_Mask)) | (_Val)) #define CHECK_RM_STATE(_pHdr, _Mask, _Val) \ ((((_pHdr)->RmState) & (_Mask)) == (_Val)) #define RMISALLOCATED(_pHdr) \ CHECK_RM_STATE((_pHdr), RMOBJSTATE_ALLOCMASK, RMOBJSTATE_ALLOCATED) #define SET_RM_TASK_STATE(_pTask, _pState) \ SET_RM_STATE(&(_pTask)->Hdr, RMTSKSTATE_MASK, (_pState)) #define CHECK_RM_TASK_STATE(_pTask, _pState) \ CHECK_RM_STATE(&(_pTask)->Hdr, RMTSKSTATE_MASK, (_pState)) #define GET_RM_TASK_STATE(_pTask) \ ((_pTask)->Hdr.RmState & RMTSKSTATE_MASK) #if RM_EXTRA_CHECKING #define RMPRIVATELOCK(_pobj, _psr) \ rmLock(&(_pobj)->RmPrivateLock, 0, rmPrivateLockVerifier, (_pobj), (_psr)) #else // !RM_EXTRA_CHECKING #define RMPRIVATELOCK(_pobj, _psr) \ rmLock(&(_pobj)->RmPrivateLock, (_psr)) #endif // !RM_EXTRA_CHECKING #define RMPRIVATEUNLOCK(_pobj, _psr) \ rmUnlock(&(_pobj)->RmPrivateLock, (_psr)) #if 0 #define RM_TEST_SIG 0x59dcfd36 #define RM_TEST_DEALLOC_SIG 0x21392147 #define RM_OBJECT_IS_ALLOCATED(_pobj) \ ((_pobj)->Sig == RM_TEST_SIG) #define RM_MARK_OBJECT_AS_DEALLOCATED(_pobj) \ ((_pobj)->Sig = RM_TEST_DEALLOC_SIG) #else #define RM_OBJECT_IS_ALLOCATED(_pobj) 0x1 #define RM_MARK_OBJECT_AS_DEALLOCATED(_pobj) (0) #endif //========================================================================= // L O C A L P R O T O T Y P E S //========================================================================= #if RM_EXTRA_CHECKING // The lowest AssociationID used internal to the RM API implementation. // #define RM_PRIVATE_ASSOC_BASE (0x1<<31) // Association types internal to RM API impmenentation. // enum { RM_PRIVATE_ASSOC_LINK = RM_PRIVATE_ASSOC_BASE, RM_PRIVATE_ASSOC_LINK_CHILDOF, RM_PRIVATE_ASSOC_LINK_PARENTOF, RM_PRIVATE_ASSOC_LINK_TASKPENDINGON, RM_PRIVATE_ASSOC_LINK_TASKBLOCKS, RM_PRIVATE_ASSOC_INITGROUP, RM_PRIVATE_ASSOC_RESUME_TASK_ASYNC, RM_PRIVATE_ASSOC_RESUME_TASK_DELAYED }; const char *szASSOCFORMAT_LINK = " Linked to 0x%p (%s)\n"; const char *szASSOCFORMAT_LINK_CHILDOF = " Child of 0x%p (%s)\n"; const char *szASSOCFORMAT_LINK_PARENTOF = " Parent of 0x%p (%s)\n"; const char *szASSOCFORMAT_LINK_TASKPENDINGON = " Pending on 0x%p (%s)\n"; const char *szASSOCFORMAT_LINK_TASKBLOCKS = " Blocks 0x%p (%s)\n"; const char *szASSOCFORMAT_INITGROUP = " Owns group 0x%p (%s)\n"; const char *szASSOCFORMAT_RESUME_TASK_ASYNC = " Resume async (param=0x%p)\n"; const char *szASSOCFORMAT_RESUME_TASK_DELAYED = " Resume delayed (param=0x%p)\n"; // Linked to 0x098889(LocalIP) // Parent of 0x098889(InitIPTask) // Child of 0x098889(Interface) #endif // RM_EXTRA_CHECKING // Private RM task to unload all objects in a group. // typedef struct { RM_TASK TskHdr; // Common task header PRM_GROUP pGroup; // Group being unloaded UINT uIndex; // Index of hash-table currently being // unloaded. NDIS_EVENT BlockEvent; // Event to optionally signal when done. BOOLEAN fUseEvent; // TRUE IFF event is to be signaled. PFN_RM_TASK_HANDLER pfnTaskUnloadObjectHandler; // ... // Object's unload task. PFN_RM_TASK_ALLOCATOR pfnUnloadTaskAllocator; } TASK_UNLOADGROUP; // // RM_PRIVATE_TASK is the union of all tasks structures used intenally in rm.c. // rmAllocateTask allocates memory of sizeof(RM_PRIVATE_TASK), which is guaranteed // to be large enough to hold any task internal to rm.c // typedef union { RM_TASK TskHdr; TASK_UNLOADGROUP UnloadGroup; } RM_PRIVATE_TASK; #if RM_EXTRA_CHECKING VOID rmDbgInitializeDiagnosticInfo( PRM_OBJECT_HEADER pObject, PRM_STACK_RECORD pSR ); VOID rmDbgDeinitializeDiagnosticInfo( PRM_OBJECT_HEADER pObject, PRM_STACK_RECORD pSR ); VOID rmDbgPrintOneAssociation ( PRM_HASH_LINK pLink, PVOID pvContext, PRM_STACK_RECORD pSR ); VOID _cdecl rmDefaultDumpEntry ( char *szFormatString, UINT_PTR Param1, UINT_PTR Param2, UINT_PTR Param3, UINT_PTR Param4 ); UINT rmSafeAppend( char *szBuf, const char *szAppend, UINT cbBuf ); #endif // RM_EXTRA_CHECKING NDIS_STATUS rmTaskUnloadGroup( IN struct _RM_TASK * pTask, IN RM_TASK_OPERATION Code, IN UINT_PTR UserParam, // Unused IN PRM_STACK_RECORD pSR ); NDIS_STATUS rmAllocatePrivateTask( IN PRM_OBJECT_HEADER pParentObject, IN PFN_RM_TASK_HANDLER pfnHandler, IN UINT Timeout, IN const char * szDescription, OPTIONAL OUT PRM_TASK *ppTask, IN PRM_STACK_RECORD pSR ); VOID rmWorkItemHandler_ResumeTaskAsync( IN PNDIS_WORK_ITEM pWorkItem, IN PVOID pTaskToResume ); VOID rmTimerHandler_ResumeTaskDelayed( IN PVOID SystemSpecific1, IN PVOID FunctionContext, IN PVOID SystemSpecific2, IN PVOID SystemSpecific3 ); VOID rmPrivateTaskDelete ( PRM_OBJECT_HEADER pObj, PRM_STACK_RECORD psr ); VOID rmDerefObject( PRM_OBJECT_HEADER pObject, PRM_STACK_RECORD pSR ); VOID rmLock( PRM_LOCK pLock, #if RM_EXTRA_CHECKING UINT uLocID, PFNLOCKVERIFIER pfnVerifier, PVOID pVerifierContext, #endif //RM_EXTRA_CHECKING PRM_STACK_RECORD pSR ); VOID rmUnlock( PRM_LOCK pLock, PRM_STACK_RECORD pSR ); #if RM_EXTRA_CHECKING ULONG rmPrivateLockVerifier( PRM_LOCK pLock, BOOLEAN fLock, PVOID pContext, PRM_STACK_RECORD pSR ); ULONG rmVerifyObjectState( PRM_LOCK pLock, BOOLEAN fLock, PVOID pContext, PRM_STACK_RECORD pSR ); RM_DBG_LOG_ENTRY * rmDbgAllocateLogEntry(VOID); VOID rmDbgDeallocateLogEntry( RM_DBG_LOG_ENTRY *pLogEntry ); #endif // RM_EXTRA_CHECKING VOID rmEndTask( PRM_TASK pTask, NDIS_STATUS Status, PRM_STACK_RECORD pSR ); VOID rmUpdateHashTableStats( PULONG pStats, ULONG LinksTraversed ); typedef struct { PFN_RM_GROUP_ENUMERATOR pfnObjEnumerator; PVOID pvCallerContext; INT fContinue; } RM_STRONG_ENUMERATION_CONTEXT, *PRM_STRONG_ENUMERATION_CONTEXT; typedef struct { PRM_OBJECT_HEADER *ppCurrent; PRM_OBJECT_HEADER *ppEnd; } RM_WEAK_ENUMERATION_CONTEXT, *PRM_WEAK_ENUMERATION_CONTEXT; VOID rmEnumObjectInGroupHashTable ( PRM_HASH_LINK pLink, PVOID pvContext, PRM_STACK_RECORD pSR ); VOID rmConstructGroupSnapshot ( PRM_HASH_LINK pLink, PVOID pvContext, PRM_STACK_RECORD pSR ); //========================================================================= // L O C A L D A T A //========================================================================= // Global struture for the RM apis. // struct { // Accessed via interlocked operation. // ULONG Initialized; RM_OS_LOCK GlobalOsLock; LIST_ENTRY listGlobalLog; UINT NumGlobalLogEntries; } RmGlobals; RM_STATIC_OBJECT_INFO RmPrivateTasks_StaticInfo = { 0, // TypeUID 0, // TypeFlags "RM Private Task", // TypeName 0, // Timeout NULL, // pfnCreate rmPrivateTaskDelete, // pfnDelete NULL, // LockVerifier 0, // length of resource table NULL // Resource Table }; // TODO: make constant static RM_STATIC_OBJECT_INFO RmTask_StaticInfo = { 0, // TypeUID 0, // TypeFlags "Task", // TypeName 0, // Timeout NULL, // Create NULL, // Delete NULL, // LockVerifier 0, // ResourceTable size NULL // ResourceTable }; //========================================================================= // R M A P I S //========================================================================= #define RM_INITIALIZATION_STARTING 1 #define RM_INITIALIZATION_COMPLETE 2 VOID RmInitializeRm(VOID) /*++ Must be called before any RM APIs are called. TODO: replace by registration mechanism. See notes.txt 03/07/1999 entry "Registering root objects with RM". --*/ { ENTER("RmInitializeRm", 0x29f5d167) if (InterlockedCompareExchange( &RmGlobals.Initialized, RM_INITIALIZATION_STARTING, 0)==0) { TR_INFO(("Initializing RM APIs Global Info\n")); NdisAllocateSpinLock(&RmGlobals.GlobalOsLock); InitializeListHead(&RmGlobals.listGlobalLog); InterlockedExchange(&RmGlobals.Initialized, RM_INITIALIZATION_COMPLETE); } else { // Spin waiting for it to get to RM_INITIALIZATION_COMPLETE (allocated). TR_INFO(("Spinning, waiting for initialization to complete.\n")); while (RmGlobals.Initialized != RM_INITIALIZATION_COMPLETE) { // spin } } EXIT() } VOID RmDeinitializeRm(VOID) /*++ Must be called to deinitialze, after last RM api is called and all async activity is over. TODO: replace by deregistration mechanism. See notes.txt 03/07/1999 entry "Registering root objects with RM". --*/ { ENTER("RmDeinitializeRm", 0x9a8407e9) ASSERT(RmGlobals.Initialized == RM_INITIALIZATION_COMPLETE); TR_INFO(("Deinitializing RM APIs Global Info\n")); // Make sure global log list is empty. Acquiring the GLobalOsLock is // not necessary here because all activity has stopped by now. // ASSERT(IsListEmpty(&RmGlobals.listGlobalLog)); EXIT() } VOID RmInitializeHeader( IN PRM_OBJECT_HEADER pParentObject, IN PRM_OBJECT_HEADER pObject, IN UINT Sig, IN PRM_LOCK pLock, IN PRM_STATIC_OBJECT_INFO pStaticInfo, IN const char * szDescription, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Initialize the RM_OBJECT_HEADER portion of an object. Arguments: pParentObject - NULL for a root object. pObject - Object to be initialized. Sig - Signature of the object. pLock - Lock used to serialize access to the object. pStaticInfo - Static informatation about the object. szDescription - A discriptive string (for debugging only) to be associated with this object. --*/ { ENTER("RmInitializeHeader", 0x47dea382) NdisZeroMemory(pObject, sizeof(*pObject)); if (szDescription == NULL) { szDescription = pStaticInfo->szTypeName; } TR_VERB(("Initializing header 0x%p (%s)\n", pObject, szDescription)); pObject->Sig = Sig; pObject->pLock = pLock; pObject->pStaticInfo = pStaticInfo; pObject->szDescription = szDescription; SET_RM_STATE(pObject, RMOBJSTATE_ALLOCMASK, RMOBJSTATE_ALLOCATED); // The private lock is set to level (UINT)-1, which is the highest // possible level. // RmInitializeLock(&pObject->RmPrivateLock, (UINT)-1); #if RM_EXTRA_CHECKING rmDbgInitializeDiagnosticInfo(pObject, pSR); #endif //RM_EXTRA_CHECKING // Link to parent if non NULL. // if (pParentObject != NULL) { pObject->pParentObject = pParentObject; pObject->pRootObject = pParentObject->pRootObject; #if RM_EXTRA_CHECKING RmLinkObjectsEx( pObject, pParentObject, 0x11f25620, RM_PRIVATE_ASSOC_LINK_CHILDOF, szASSOCFORMAT_LINK_CHILDOF, RM_PRIVATE_ASSOC_LINK_PARENTOF, szASSOCFORMAT_LINK_PARENTOF, pSR ); #else // !RM_EXTRA_CHECKING RmLinkObjects(pObject, pParentObject, pSR); #endif // !RM_EXTRA_CHECKING } else { pObject->pRootObject = pObject; } // We increment the total-ref count once for the allocation. This // reference is removed in the call to RmDeallocateObject. // Note that this reference is in addition to the reference implicitly // added by the call to RmLinkObjects above. // NdisInterlockedIncrement(&pObject->TotRefs); #if RM_TRACK_OBJECT_TREE // Initialize our list of children. // InitializeListHead(&pObject->listChildren); if (pParentObject != NULL) { // Insert ourselves into our parent's list of children. // RMPRIVATELOCK(pParentObject, pSR); InsertHeadList(&pParentObject->listChildren, &pObject->linkSiblings); RMPRIVATEUNLOCK(pParentObject, pSR); } #endif // RM_TRACK_OBJECT_TREE EXIT() return; } VOID RmDeallocateObject( IN PRM_OBJECT_HEADER pObject, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Logically deallocate the object pObject. We don't actually unlink it from its parent or deallocate if there are non zero references to it. --*/ { UINT Refs; ENTER("RmDeallocateObject", 0xa87fdf4a) TR_INFO(("0x%p (%s)\n", pObject, pObject->szDescription)); RMPRIVATELOCK(pObject, pSR); RETAILASSERTEX(RMISALLOCATED(pObject), pObject); // Set state to deallocated. // SET_RM_STATE(pObject, RMOBJSTATE_ALLOCMASK, RMOBJSTATE_DEALLOCATED); RMPRIVATEUNLOCK(pObject, pSR); // Remove the ref explicitly added in RmInitializeAllocateObject. // rmDerefObject will remove the link to the parent, if any, if the // ref count drop to 1. // rmDerefObject(pObject, pSR); EXIT() } VOID RmInitializeLock( IN PRM_LOCK pLock, IN UINT Level ) /*++ Routine Description: Initialize a lock. Arguments: pLock - Unitialized memory to hold a struct of type RM_LOCK. Level - Level to be associated with this lock. Locks must be acquired in strictly increasing order of the locks' "Level" values. --*/ { ASSERT(Level > 0); NdisAllocateSpinLock(&pLock->OsLock); pLock->Level = Level; #if RM_EXTRA_CHECKING pLock->pDbgInfo = &pLock->DbgInfo; NdisZeroMemory(&pLock->DbgInfo, sizeof(pLock->DbgInfo)); #endif // RM_EXTRA_CHECKING } VOID RmDoWriteLock( PRM_LOCK pLock, PRM_STACK_RECORD pSR ) /*++ Routine Description: Acquire (write lock) lock pLock. --*/ { rmLock( pLock, #if RM_EXTRA_CHECKING 0x16323980, // uLocID, NULL, NULL, #endif //RM_EXTRA_CHECKING pSR ); } VOID RmDoUnlock( PRM_LOCK pLock, PRM_STACK_RECORD pSR ) /*++ Routine Description: Unlock lock pLock. --*/ { rmUnlock( pLock, pSR ); } #if TODO // Currently RmReadLockObject is a macro defined to be RmWriteLockObject. // TODO: Verifier need to to also make sure that object hasn't changed state // *while* the object has been read-locked. VOID RmReadLockObject( IN PRM_OBJECT_HEADER pObj, #if RM_EXTRA_CHECKING UINT uLocID, #endif //RM_EXTRA_CHECKING IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Acquire (read lock) lock pLock. --*/ { ASSERT(!"Unimplemented"); } #endif //TODO VOID RmWriteLockObject( IN PRM_OBJECT_HEADER pObj, #if RM_EXTRA_CHECKING UINT uLocID, #endif //RM_EXTRA_CHECKING IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Acquire (write lock) the lock associated with object pObj. Arguments: pObj -- Object whose lock to acquire. uLocID -- Arbitrary UINT identifying static location from which this call is made. --*/ { ENTER("RmWriteLockObject", 0x590ed543) TR_VERB(("Locking 0x%p (%s)\n", pObj, pObj->szDescription)); rmLock( pObj->pLock, #if RM_EXTRA_CHECKING uLocID, // pObj->pStaticInfo->pfnLockVerifier, rmVerifyObjectState, pObj, #endif //RM_EXTRA_CHECKING pSR ); EXIT() } VOID RmUnlockObject( IN PRM_OBJECT_HEADER pObj, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Release the lock associated with object pObj. --*/ { ENTER("RmUnLockObject", 0x0307dd84) TR_VERB(("Unlocking 0x%p (%s)\n", pObj, pObj->szDescription)); #if RM_EXTRA_CHECKING // // Make sure that pObject is the object that is *supposed* to be freed. // ASSERT(pSR->LockInfo.pNextFree[-1].pVerifierContext == (PVOID) pObj); #endif // RM_EXTRA_CHECKING rmUnlock( pObj->pLock, pSR ); EXIT() } VOID RmUnlockAll( IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Unlocks all currently held locks as recorded in pSR. If the locks are associated with objects, RmUnlockObject is called for each of the held locks. Otherwise the raw unlock is performed. --*/ { ENTER("RmUnLockObject", 0x9878be96) TR_VERB(("Unlocking all\n")); while (pSR->LockInfo.CurrentLevel != 0) { rmUnlock( pSR->LockInfo.pNextFree[-1].pLock, pSR ); } EXIT() } VOID RmDbgChangeLockScope( IN PRM_OBJECT_HEADER pPreviouslyLockedObject, IN PRM_OBJECT_HEADER pObject, IN ULONG LocID, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: (Debug only) Munge things so that the following sequence works: Rm[Read|Write]LockObject(pPreviouslyLockedObject, pSR); RmChangeLockScope(pPreviouslyLockedObject, pSR); RmUnlockObject(pObject, pSR); Of course, we require that the two objects have the same lock! NOTE: We only support changing scope of the MOST RECENTLY acquired lock. Arguments: pPreviouslyLockedObject - Currently locked object. pObject - Object to transfer lock scope to. LocID - Arbitrary UINT identifying static location from which this call is made. --*/ { // // This is a NOOP unless extra-checking is enabled. // TODO: make this inline in the fre build. // #if RM_EXTRA_CHECKING RM_LOCKING_INFO * pLI = pSR->LockInfo.pNextFree-1; PRM_LOCK pLock = pPreviouslyLockedObject->pLock; ASSERT( pLock->Level == pSR->LockInfo.CurrentLevel && pLock == pObject->pLock && pLock == pLI->pLock && pLI->pVerifierContext == (PVOID) pPreviouslyLockedObject); ASSERT(pLI->pfnVerifier == rmVerifyObjectState); rmVerifyObjectState(pLock, FALSE, pLI->pVerifierContext, pSR); pLI->pVerifierContext = pObject; pLock->DbgInfo.uLocID = LocID; rmVerifyObjectState(pLock, TRUE, pLI->pVerifierContext, pSR); #endif // RM_EXTRA_CHECING } VOID RmLinkObjects( IN PRM_OBJECT_HEADER pObj1, IN PRM_OBJECT_HEADER pObj2, IN PRM_STACK_RECORD pSr ) /*++ Routine Description: Link object pObj1 to object pObj2. Basically, this function refs both objects. OK to call with some locks held, including RmPrivateLock. TODO: remove arp pSr above -- we don't need it. --*/ { ENTER("RmLinkObjects", 0xfe2832dd) // Maybe we're being too harsh here -- if required, remove this... // This could happen where a task is linked at the point where the object // is being deallocated, so I'm changing the following to debug asserts // (used to be retail asserts). // ASSERT(RMISALLOCATED(pObj1)); ASSERT(RMISALLOCATED(pObj2)); TR_INFO(("0x%p (%s) linked to 0x%p (%s)\n", pObj1, pObj1->szDescription, pObj2, pObj2->szDescription )); NdisInterlockedIncrement(&pObj1->TotRefs); NdisInterlockedIncrement(&pObj2->TotRefs); } VOID RmLinkObjectsEx( IN PRM_OBJECT_HEADER pObj1, IN PRM_OBJECT_HEADER pObj2, IN ULONG LocID, IN ULONG AssocID, IN const char * szAssociationFormat, IN ULONG InvAssocID, IN const char * szInvAssociationFormat, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Same as RmLinkObjects, execpt that (debug only) it also sets up association (pObj2, pObj2->szDescription, AssocID) on pObj1, and association (pObj1, pObj1->szDescription, InvAssocID) on object pObj2 Arguments: pObj1 - Object whose lock to acquire. pObj2 - Object whose lock to acquire. LocID - Arbitrary UINT identifying static location from which this call is made. AssocID - ID of the association (see RmDbgAddAssociation) that represents the link from pObj1 to pObj2. szAssociationFormat - Format of the association (see RmDbgAddAssociation) InvAssocId - ID of the inverse association (i.e., represents link from pObj2 to pObj1). szInvAssociationFormat - Format of the inverse association. --*/ { ENTER("RmLinkObjectsEx", 0xef50263b) #if RM_EXTRA_CHECKING RmDbgAddAssociation( LocID, // Location ID pObj1, // pObject (UINT_PTR) pObj2, // Instance1 (UINT_PTR) (pObj2->szDescription), // Instance2 AssocID, // AssociationID szAssociationFormat, pSR ); RmDbgAddAssociation( LocID, // Location ID pObj2, // pObject (UINT_PTR) pObj1, // Instance1 (UINT_PTR) (pObj1->szDescription), // Instance2 InvAssocID, // AssociationID szInvAssociationFormat, pSR ); #endif // RM_EXTRA_CHECKING RmLinkObjects( pObj1, pObj2, pSR ); } VOID RmUnlinkObjects( IN PRM_OBJECT_HEADER pObj1, IN PRM_OBJECT_HEADER pObj2, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Unlink objects pObj1 and pObj2 (i.e., undo the effect of RmLinkObjects(pObj1, pObj2, pSR)). --*/ { ENTER("RmUnlinkObjects", 0x7c64356a) TR_INFO(("0x%p (%s) unlinked from 0x%p (%s)\n", pObj1, pObj1->szDescription, pObj2, pObj2->szDescription )); #if RM_EXTRA_CHECKING // // TODO: remove explict link // #endif // RM_EXTRA_CHECKING // Remove link refs. // rmDerefObject(pObj1, pSR); rmDerefObject(pObj2, pSR); } VOID RmUnlinkObjectsEx( IN PRM_OBJECT_HEADER pObj1, IN PRM_OBJECT_HEADER pObj2, IN ULONG LocID, IN ULONG AssocID, IN ULONG InvAssocID, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Same as RmUnlinkObjects, execpt that it also removes the association (pObj2, pObj2->szDescription, AssocID) on pObj1, and association (pObj1, pObj1->szDescription, InvAssocID) object pObj2 Arguments: See RmLinkObjectsEx. --*/ { ENTER("RmUnlinkObjectsEx", 0x65d3536c) #if RM_EXTRA_CHECKING RmDbgDeleteAssociation( LocID, // Location ID pObj1, // pObject (UINT_PTR) pObj2, // Instance1 (UINT_PTR) (pObj2->szDescription), // Instance2 AssocID, // AssociationID pSR ); RmDbgDeleteAssociation( LocID, // Location ID pObj2, // pObject (UINT_PTR) pObj1, // Instance1 (UINT_PTR) (pObj1->szDescription), // Instance2 InvAssocID, // AssociationID pSR ); #endif // RM_EXTRA_CHECKING RmUnlinkObjects( pObj1, pObj2, pSR ); } VOID RmLinkToExternalEx( IN PRM_OBJECT_HEADER pObj, IN ULONG LocID, IN UINT_PTR ExternalEntity, IN ULONG AssocID, IN const char * szAssociationFormat, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Links object pObj to external entity ExternalEntity. Basically, this function adds a reference to pObj. In addition (debug only) this function sets up an association on pObj that links the external entity to pObj. pObj can be linked to ExternalEntity with the specified association ID AssocID only ONCE at any particular point of time. Once this link is setup, an attempt to deallocate pObj without removing the link results in an assertion failure. RmUnlinkFromExternalEx is the inverse function. Arguments: pObj - Object to be linked to an external entity. (following are for debug only...) LocID - Arbitrary UINT identifying static location from which this call is made. ExternalEntity - Opaque value representing the external entity. AssocID - Association ID representing the linkage. szAssociationFormat - Association format for the linkage. --*/ { ENTER("RmLinkToExternalEx", 0x9aeaca74) #if RM_EXTRA_CHECKING RmDbgAddAssociation( LocID, // Location ID pObj, // pObject (UINT_PTR) ExternalEntity, // Instance1 (UINT_PTR) 0, // Instance2 (unused) AssocID, // AssociationID szAssociationFormat, pSR ); #endif // RM_EXTRA_CHECKING RmLinkToExternalFast(pObj); EXIT() } VOID RmUnlinkFromExternalEx( IN PRM_OBJECT_HEADER pObj, IN ULONG LocID, IN UINT_PTR ExternalEntity, IN ULONG AssocID, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Inverse of RmUnlinkFromExternalEx -- removes the link set up between pObj and ExternalEntity. Arguments: See RmLinkToExternalEx. --*/ { ENTER("RmUnlinkFromExternalEx", 0x9fb084c3) #if RM_EXTRA_CHECKING RmDbgDeleteAssociation( LocID, // Location ID pObj, // pObject (UINT_PTR) ExternalEntity, // Instance1 (UINT_PTR) 0, // Instance2 (unused) AssocID, // AssociationID pSR ); #endif // RM_EXTRA_CHECKING RmUnlinkFromExternalFast(pObj); EXIT() } VOID RmLinkToExternalFast( // TODO make inline IN PRM_OBJECT_HEADER pObj ) /*++ Routine Description: Fast version of RmLinkToExternalEx -- same behavior in retail. No associations are setup. Arguments: See RmLinkToExternalEx. --*/ { NdisInterlockedIncrement(&pObj->TotRefs); } VOID RmUnlinkFromExternalFast( // TODO make inline IN PRM_OBJECT_HEADER pObj ) /*++ Routine Description: Inverse of RmUnlinkFromExternalFast -- removes the link set up between pObj and ExternalEntity. TODO -- we need a fast implementation for the case that the object is not going to go away. For now we actually declare a stack record here each time, becaues rmDerefObject wants one! Bad bad. Arguments: See RmLinkToExternalFast. --*/ { RM_DECLARE_STACK_RECORD(sr) rmDerefObject(pObj, &sr); } VOID RmTmpReferenceObject( IN PRM_OBJECT_HEADER pObj, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Add a temporary reference to object pObj. (Debug only) Increments the count of tmprefs maintained in pSR. --*/ { ENTER("RmTmpReferenceObject", 0xdd981024) TR_VERB(("0x%p (%s)\n", pObj, pObj->szDescription)); ASSERT(RM_OBJECT_IS_ALLOCATED(pObj)); pSR->TmpRefs++; NdisInterlockedIncrement(&pObj->TotRefs); } VOID RmTmpDereferenceObject( IN PRM_OBJECT_HEADER pObj, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Remove a temporary reference to object pObj. (Debug only) Decrements the count of tmprefs maintained in pSR. --*/ { ENTER("RmTmpDereferenceObject", 0xd1630c11) TR_VERB(("0x%p (%s)\n", pObj, pObj->szDescription)); RETAILASSERTEX(pSR->TmpRefs>0, pSR); pSR->TmpRefs--; rmDerefObject(pObj, pSR); } VOID RmDbgAddAssociation( IN ULONG LocID, IN PRM_OBJECT_HEADER pObject, IN ULONG_PTR Entity1, IN ULONG_PTR Entity2, IN ULONG AssociationID, IN const char * szFormatString, OPTIONAL IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Add an arbitrary association, for debugging purposes, under object pObject. The association is defined by the triple (Entity1, Entity2, AssociationID) -- only ONE such tuple may be registered at any time with object pParentObject. Note: It is valid for both of the following to be registered at the same time: (a, b, 1) and (a, b, 2) No association should exist at the point the object is deleted. Arguments: LocID - Arbitrary ID, typically representing the source location - from which this function is called. pObject - Object to add the association. Entity1 - The 1st entity making up the association. May be NULL. Entity2 - The 2nd entity making up the association. May be NULL. AssociationID - ID defining the association. NOTE: AssociationID must not have the high-bit set. Associations with the high bit set are reserved for internal use of the Rm API implementation. --*/ { #if RM_EXTRA_CHECKING PRM_OBJECT_DIAGNOSTIC_INFO pDiagInfo = pObject->pDiagInfo; ENTER("RmDbgAddAssociation", 0x512192eb) if (pDiagInfo) { // // Allocate an association and enter it into the hash table. // Assert if it already exists. // RM_PRIVATE_DBG_ASSOCIATION *pA; RM_ALLOCSTRUCT(pA, MTAG_DBGINFO); // TODO use lookaside lists. if (pA == NULL) { // // Allocation failed. Record this fact, so that // RmDbgDeleteAssociation doesn't assert // if an attempt is made to remove an assertion which doesn't exist. // NdisAcquireSpinLock(&pDiagInfo->OsLock); pDiagInfo->AssociationTableAllocationFailure = TRUE; NdisReleaseSpinLock(&pDiagInfo->OsLock); } else { BOOLEAN fFound; PRM_HASH_LINK *ppLink; RM_ZEROSTRUCT(pA); pA->Entity1 = Entity1; pA->Entity2 = Entity2; pA->AssociationID = AssociationID; if (szFormatString == NULL) { // Put in the default description format string. // szFormatString = " Association (E1=0x%x, E2=0x%x, T=0x%x)\n"; } TR_VERB((" Obj:0x%p (%s)...\n", pObject, pObject->szDescription)); TRACE0(TL_INFO,((char*)szFormatString, Entity1, Entity2, AssociationID)); pA->szFormatString = szFormatString; NdisAcquireSpinLock(&pDiagInfo->OsLock); fFound = RmLookupHashTable( &pDiagInfo->AssociationTable, &ppLink, pA // We use pA as the key. ); if (fFound) { ASSERTEX( !"Association already exists:", CONTAINING_RECORD(*ppLink, RM_PRIVATE_DBG_ASSOCIATION, HashLink) ); RM_FREE(pA); pA = NULL; } else { // // Enter the association into the hash table. // RmAddHashItem( &pDiagInfo->AssociationTable, ppLink, &pA->HashLink, pA // We use pA as the key ); } NdisReleaseSpinLock(&pDiagInfo->OsLock); // Now, just for grins, make a note of this in the object's log. // WARNING: Although pEntity1/2 may contain pointers, // we expect the the format string is such that if there are any // references to regular or unicode strings, those strings will // be valid for the life of the object (typically these strings // are statically-allocated strings). // // We COULD use the more conservative format string to display the // log entry, but it's useful to have the information displayed // properly. // // Note-- we could also do different things depending on the type // of association. // #if 0 // conservative format RmDbgLogToObject( pObject, " Add association (E1=0x%x, E2=0x%x, T=0x%x)\n", Entity1, Entity2, AssociationID, 0, // Param4 // (UINT_PTR) szFormatString, NULL, NULL ); #else // aggresive format { #define szADDASSOC " Add assoc:" #if OBSOLETE // This doesn't work because rgMungedFormat is on the stack! char rgMungedFormat[128]; UINT uLength; rgMungedFormat[0]=0; rmSafeAppend(rgMungedFormat, szADDASSOC, sizeof(rgMungedFormat)); uLength = rmSafeAppend( rgMungedFormat, szFormatString, sizeof(rgMungedFormat) ); if (uLength && rgMungedFormat[uLength-1] != '\n') { rgMungedFormat[uLength-1] = '\n'; } RmDbgLogToObject( pObject, rgMungedFormat, Entity1, Entity2, AssociationID, 0, NULL, NULL ); #endif // OBSOLETE RmDbgLogToObject( pObject, szADDASSOC, (char*)szFormatString, Entity1, Entity2, AssociationID, 0, NULL, NULL ); } #endif // aggressive format } } EXIT() #endif // RM_EXTRA_CHECKING } VOID RmDbgDeleteAssociation( IN ULONG LocID, IN PRM_OBJECT_HEADER pObject, IN ULONG_PTR Entity1, IN ULONG_PTR Entity2, IN ULONG AssociationID, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Removes the previously-added association (Entity1, Entity2, Association) from object pObject. See the description of RmDbgAddAssociation for details. Arguments: See RmDbgAddAssociation. --*/ { #if RM_EXTRA_CHECKING ENTER("RmDbgDelAssociation", 0x8354559f) PRM_OBJECT_DIAGNOSTIC_INFO pDiagInfo = pObject->pDiagInfo; if (pDiagInfo) { BOOLEAN fFound; PRM_HASH_LINK *ppLink; RM_PRIVATE_DBG_ASSOCIATION TrueKey; // Only the following 3 fields of TrueKey make up the key // TrueKey.Entity1 = Entity1; TrueKey.Entity2 = Entity2; TrueKey.AssociationID = AssociationID; NdisAcquireSpinLock(&pDiagInfo->OsLock); fFound = RmLookupHashTable( &pDiagInfo->AssociationTable, &ppLink, &TrueKey ); if (fFound) { RM_PRIVATE_DBG_ASSOCIATION *pA = CONTAINING_RECORD(*ppLink, RM_PRIVATE_DBG_ASSOCIATION, HashLink); TR_VERB((" Obj:0x%p (%s)...\n", pObject, pObject->szDescription)); TRACE0(TL_INFO, ((char*)pA->szFormatString, pA->Entity1, pA->Entity2, pA->AssociationID)); // // Now, just for grins, make a note of this in the oject's log. // Note that of pEntity1/2 contain pointers, we can't expect them // to be valid for as long as the object is alive, so we use // the more conservative format string to display the log entry. // // TODO/BUGUG -- see comments under RmDbgAddAssociation // about the risk of directly passing szFormat // #if 0 // conservative RmDbgLogToObject( pObject, NULL, " Deleted Association (E1=0x%x, E2=0x%x, T=0x%x)\n", pA->Entity1, pA->Entity2, pA->AssociationID, 0, NULL, NULL ); #else // aggressive #define szDELASSOC " Del assoc:" RmDbgLogToObject( pObject, szDELASSOC, (char*) pA->szFormatString, pA->Entity1, pA->Entity2, pA->AssociationID, 0, // Param4 // (UINT_PTR) szFormatString, NULL, NULL ); #endif // aggressive // // Remove the association and free it. // RM_PRIVATE_UNLINK_NEXT_HASH( &pDiagInfo->AssociationTable, ppLink ); RM_FREE(pA); } else { if (!pDiagInfo->AssociationTableAllocationFailure) { ASSERT(!"Association doesn't exist"); } } NdisReleaseSpinLock(&pDiagInfo->OsLock); } EXIT() #endif // RM_EXTRA_CHECKING } VOID RmDbgPrintAssociations( PRM_OBJECT_HEADER pObject, PRM_STACK_RECORD pSR ) /*++ Routine Description: (Debug only) Dumps the associations on object pObject. --*/ { #if RM_EXTRA_CHECKING ENTER("RmPrintAssociations", 0x8354559f) PRM_OBJECT_DIAGNOSTIC_INFO pDiagInfo = pObject->pDiagInfo; if (pDiagInfo) { TR_INFO(( "Obj 0x%p (%s):\n", pObject, pObject->szDescription )); NdisAcquireSpinLock(&pDiagInfo->OsLock); RmEnumHashTable( &pDiagInfo->AssociationTable, rmDbgPrintOneAssociation, // pfnEnumerator pObject, // context pSR ); NdisReleaseSpinLock(&pDiagInfo->OsLock); } EXIT() #endif // RM_EXTRA_CHECKING } // // Diagnostic per-object logging. // VOID RmDbgLogToObject( IN PRM_OBJECT_HEADER pObject, IN char * szPrefix, OPTIONAL IN char * szFormatString, IN UINT_PTR Param1, IN UINT_PTR Param2, IN UINT_PTR Param3, IN UINT_PTR Param4, IN PFN_DBG_DUMP_LOG_ENTRY pfnDumpEntry, OPTIONAL IN PVOID pvBuf OPTIONAL ) /*++ Routine Description: Make one log entry in pObject's log. TODO: See notes.txt entry "03/07/1999 ... Registering root objects with RM" on how we will find the deallocator function fo pvBuf. For now we simply use NdisFreeMemory. TODO: need to implement trimming of log when we reach a maximum. Currently we just stop logging. Arguments: pfnDumpEntry - Function to be used for dumping the log. If NULL, a default function is used, which interprets szFormatString as the standard printf format string. szFormatString - Format string for log display -- 1st arg to pfnDumpEntry Param1-4 - Remaining args to pfnDumpEntry; pvBuf - If non-NULL, piece of memory to be freed when the log entry is freed. NOTE: If Param1-4 contain pointers, the memory they refer to is assumed to be valid for as long as the object is alive. If the entities being logged may go away before the object is deallocated, the caller should allocate a buffer to hold a copy of the entities, and pass the pointer to that buffer as pvBuf. --*/ { #if RM_EXTRA_CHECKING ENTER("RmDbgLogToObject", 0x2b2015b5) PRM_OBJECT_DIAGNOSTIC_INFO pDiagInfo = pObject->pDiagInfo; if (pDiagInfo && RmGlobals.NumGlobalLogEntries < 4000) { RM_DBG_LOG_ENTRY *pLogEntry; NdisAcquireSpinLock(&RmGlobals.GlobalOsLock); pLogEntry = rmDbgAllocateLogEntry(); if (pLogEntry != NULL) { if (pfnDumpEntry == NULL) { pfnDumpEntry = rmDefaultDumpEntry; } pLogEntry->pObject = pObject; pLogEntry->pfnDumpEntry = pfnDumpEntry; pLogEntry->szPrefix = szPrefix; pLogEntry->szFormatString = szFormatString; pLogEntry->Param1 = Param1; pLogEntry->Param2 = Param2; pLogEntry->Param3 = Param3; pLogEntry->Param4 = Param4; pLogEntry->pvBuf = pvBuf; // Insert item at head of object log. // InsertHeadList(&pDiagInfo->listObjectLog, &pLogEntry->linkObjectLog); // Insert item at head of global log. // InsertHeadList(&RmGlobals.listGlobalLog, &pLogEntry->linkGlobalLog); pDiagInfo->NumObjectLogEntries++; RmGlobals.NumGlobalLogEntries++; } NdisReleaseSpinLock(&RmGlobals.GlobalOsLock); #if 0 pfnDumpEntry( szFormatString, Param1, Param2, Param3, Param4 ); #endif // 0 } else { // TODO -- free pvBuf if NON NULL. } EXIT() #endif // RM_EXTRA_CHECKING } VOID RmDbgPrintObjectLog( IN PRM_OBJECT_HEADER pObject ) /*++ Routine Description: (Debug only) Dumps object pObject's log. --*/ { #if RM_EXTRA_CHECKING ENTER("RmPrintObjectLog", 0xe06507e5) PRM_OBJECT_DIAGNOSTIC_INFO pDiagInfo = pObject->pDiagInfo; TR_INFO((" pObj=0x%p (%s)\n", pObject, pObject->szDescription)); if (pDiagInfo != NULL) { LIST_ENTRY *pLink=NULL; LIST_ENTRY * pObjectLog = &pDiagInfo->listObjectLog; NdisAcquireSpinLock(&RmGlobals.GlobalOsLock); for( pLink = pObjectLog->Flink; pLink != pObjectLog; pLink = pLink->Flink) { RM_DBG_LOG_ENTRY *pLE; pLE = CONTAINING_RECORD(pLink, RM_DBG_LOG_ENTRY, linkObjectLog); if (pLE->szPrefix != NULL) { // Print the prefix. DbgPrint(pLE->szPrefix); } // Call the dump function for this entry. // // pLE->pfnDumpEntry( pLE->szFormatString, pLE->Param1, pLE->Param2, pLE->Param3, pLE->Param4 ); } NdisReleaseSpinLock(&RmGlobals.GlobalOsLock); } EXIT() #endif // RM_EXTRA_CHECKING } VOID RmDbgPrintGlobalLog(VOID) /*++ Routine Description: (Debug only) Dumps the global log (which contains entries from all object's logs. --*/ { #if RM_EXTRA_CHECKING ENTER("RmPrintGlobalLog", 0xe9915066) LIST_ENTRY *pLink=NULL; LIST_ENTRY *pGlobalLog = &RmGlobals.listGlobalLog; TR_INFO(("Enter\n")); NdisAcquireSpinLock(&RmGlobals.GlobalOsLock); for( pLink = pGlobalLog->Flink; pLink != pGlobalLog; pLink = pLink->Flink) { RM_DBG_LOG_ENTRY *pLE; pLE = CONTAINING_RECORD(pLink, RM_DBG_LOG_ENTRY, linkGlobalLog); // Print the ptr and name of the object whose entry this is... // DbgPrint( "Entry for 0x%p (%s):\n", pLE->pObject, pLE->pObject->szDescription ); if (pLE->szPrefix != NULL) { // Print the prefix. DbgPrint(pLE->szPrefix); } // Call the dump function for this entry. // // pLE->pfnDumpEntry( pLE->szFormatString, pLE->Param1, pLE->Param2, pLE->Param3, pLE->Param4 ); } NdisReleaseSpinLock(&RmGlobals.GlobalOsLock); EXIT() #endif // RM_EXTRA_CHECKING } RM_STATUS RmLoadGenericResource( IN PRM_OBJECT_HEADER pObj, IN UINT GenericResourceID, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: TODO This function is going away... --*/ { PRM_STATIC_OBJECT_INFO pSI = pObj->pStaticInfo; RM_STATUS Status; // The resource ID should be less than number of bits in the ResourceMap // ASSERT(GenericResourceID < 8*sizeof(pObj->ResourceMap)); RMPRIVATELOCK(pObj, pSR); do { UINT ResFlag = 1<NumResourceTableEntries <= GenericResourceID) { ASSERTEX(!"Invalid GenericResourceID", pObj); Status = NDIS_STATUS_FAILURE; RMPRIVATEUNLOCK(pObj, pSR); break; } // The resource entry indexed must have its ID == GenericResourceID // // if (pSI->pResourceTable[GenericResourceID].ID != GenericResourceID) { ASSERTEX(!"Resource ID doesn't match table entry", pObj); Status = NDIS_STATUS_FAILURE; RMPRIVATEUNLOCK(pObj, pSR); break; } if ( ResFlag & pObj->ResourceMap) { ASSERTEX(!"Resource already allocated", pObj); Status = NDIS_STATUS_FAILURE; RMPRIVATEUNLOCK(pObj, pSR); break; } pObj->ResourceMap |= ResFlag; RMPRIVATEUNLOCK(pObj, pSR); Status = pSI->pResourceTable[GenericResourceID].pfnHandler( pObj, RM_RESOURCE_OP_LOAD, NULL, // pvUserParams (unused) pSR ); if (FAIL(Status)) { // Clear the resource map bit on failure. // RMPRIVATELOCK(pObj, pSR); ASSERTEX(ResFlag & pObj->ResourceMap, pObj); pObj->ResourceMap &= ~ResFlag; RMPRIVATEUNLOCK(pObj, pSR); } } while (FALSE); return Status; } VOID RmUnloadGenericResource( IN PRM_OBJECT_HEADER pObj, IN UINT GenericResourceID, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: TODO This function is going away... --*/ { PRM_STATIC_OBJECT_INFO pSI = pObj->pStaticInfo; RM_STATUS Status; // The resource ID should be less than number of bits in the ResourceMap // ASSERT(GenericResourceID < 8*sizeof(pObj->ResourceMap)); RMPRIVATELOCK(pObj, pSR); do { UINT ResFlag = 1<NumResourceTableEntries <= GenericResourceID) { ASSERTEX(!"Invalid GenericResourceID", pObj); RMPRIVATEUNLOCK(pObj, pSR); break; } if ( !(ResFlag & pObj->ResourceMap)) { ASSERTEX(!"Resource not allocated", pObj); RMPRIVATEUNLOCK(pObj, pSR); break; } // Clear the resource flag. // pObj->ResourceMap &= ~ResFlag; RMPRIVATEUNLOCK(pObj, pSR); pSI->pResourceTable[GenericResourceID].pfnHandler( pObj, RM_RESOURCE_OP_UNLOAD, NULL, // pvUserParams (unused) pSR ); } while (FALSE); } VOID RmUnloadAllGenericResources( IN PRM_OBJECT_HEADER pObj, IN PRM_STACK_RECORD pSR ) /*++ Synchronously unload all previously loaded resources for this object, in reverse order to which they were loaded. TODO this function is going away... --*/ { PRM_STATIC_OBJECT_INFO pSI = pObj->pStaticInfo; RM_STATUS Status; UINT u; RMPRIVATELOCK(pObj, pSR); for(u = pSI->NumResourceTableEntries; u && pObj->ResourceMap; u--) { UINT ResID = u-1; UINT ResFlag = 1<ResourceMap)) { continue; } if (pSI->NumResourceTableEntries <= ResID) { ASSERTEX(!"Corrupt ResourceMap", pObj); RMPRIVATEUNLOCK(pObj, pSR); break; } // Clear the resource flag. // pObj->ResourceMap &= ~ResFlag; RMPRIVATEUNLOCK(pObj, pSR); pSI->pResourceTable[ResID].pfnHandler( pObj, RM_RESOURCE_OP_UNLOAD, NULL, // pvUserParams (unused) pSR ); RMPRIVATELOCK(pObj, pSR); } ASSERTEX(!pObj->ResourceMap, pObj); RMPRIVATEUNLOCK(pObj, pSR); } VOID RmInitializeGroup( IN PRM_OBJECT_HEADER pOwningObject, IN PRM_STATIC_OBJECT_INFO pStaticInfo, IN PRM_GROUP pGroup, IN const char* szDescription, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Initialize a group structure. Arguments: pOwningObject - Object that will own the group. pStaticInfo - Static information about objects IN the group. pGroup - Uninitialized memory that is to hold the group structure. It will be initialized on return from this function. szDescription - (Debug only) descriptive name for this group. TODO: make pStaticInfo const. --*/ { NdisZeroMemory(pGroup, sizeof(*pGroup)); RMPRIVATELOCK(pOwningObject, pSR); do { if (!RMISALLOCATED(pOwningObject)) { ASSERT(!"pObject not allocated"); break; } if (pStaticInfo->pHashInfo == NULL) { ASSERT(!"NULL pHashInfo"); // Static info MUST have non-NULL pHashInfo in order // for it to be used for groups. // break; } RmInitializeHashTable( pStaticInfo->pHashInfo, pOwningObject, // pAllocationContext &pGroup->HashTable ); pGroup->pOwningObject = pOwningObject; pGroup->pStaticInfo = pStaticInfo; pGroup->szDescription = szDescription; NdisAllocateSpinLock(&pGroup->OsLock); pGroup->fEnabled = TRUE; #if RM_EXTRA_CHECKING RmDbgAddAssociation( 0xc0e5362f, // Location ID pOwningObject, // pObject (UINT_PTR) pGroup, // Instance1 (UINT_PTR) (pGroup->szDescription), // Instance2 RM_PRIVATE_ASSOC_INITGROUP, // AssociationID szASSOCFORMAT_INITGROUP, // szAssociationFormat pSR ); #endif // RM_EXTRA_CHECKING } while (FALSE); RMPRIVATEUNLOCK(pOwningObject, pSR); } VOID RmDeinitializeGroup( IN PRM_GROUP pGroup, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Deinitialize group structure pGroup. Must only be called when there are no members in the group. --*/ { #if RM_EXTRA_CHECKING RmDbgDeleteAssociation( 0x1486def9, // Location ID pGroup->pOwningObject, // pObject (UINT_PTR) pGroup, // Instance1 (UINT_PTR) (pGroup->szDescription), // Instance2 RM_PRIVATE_ASSOC_INITGROUP, // AssociationID pSR ); #endif // RM_EXTRA_CHECKING NdisAcquireSpinLock(&pGroup->OsLock); RmDeinitializeHashTable(&pGroup->HashTable); NdisReleaseSpinLock(&pGroup->OsLock); NdisFreeSpinLock(&pGroup->OsLock); NdisZeroMemory(pGroup, sizeof(*pGroup)); } RM_STATUS RmLookupObjectInGroup( IN PRM_GROUP pGroup, IN ULONG Flags, // create, remove, lock IN PVOID pvKey, IN PVOID pvCreateParams, OUT PRM_OBJECT_HEADER * ppObject, OUT INT * pfCreated, OPTIONAL IN PRM_STACK_RECORD pSR ) /*++ Routine Description: TODO: split this into a pure lookup and a lookupand/orcreate function.. Lookup and/or create an object in the specified group. #if OBSOLETE // Must allow fCreate w/o locking -- see notes.txt entry: // 03/04/1999 JosephJ Problems with deadlock when using Groups. MUST ONLY be NON-NULL if the fLOCKED flag is specified. Why? Because if the lock is not held on exit, it would be possible for someone else to pick up the object in the freshly-created state. We want to discourage that situation. #endif // OBSOLETE Typically the caller specifes the fRM_LOCKED|fRM_CREATE flags as well as non-null pfCreated. On return, if *pfCreated is TRUE, the caller then would go on to do some more initialization before releasing the lock. A new important point is that if the user requests that the object be created and locked, then the RM no longer guranteees that the create and lock will happen in the context of holding the GroupLock FUNDAMENTAL ASSUMPTION: The key of an object doesn't change once it's in the group. Based on this assumption, we don't try to claim the object's lock when looking for the object with a matching key. Arguments: pGroup - Group in which to lookup/create object. Flags - One or more of fRM_LOCKED, fRM_CREATE, fRM_NEW pvKey - Key used to lookup object. pvCreateParams - If object is to be created, parameters to be passed to the object's creation function. ppObject - Place to store pointer to the found/created object. pfCreated - If non-NULL, *pfCreated is set to TRUE IFF the object was created. Return Value: NDIS_STATUS_SUCCESS If the operation succeeded. NDIS_STATUS_RESOURCES If a new object could not be created. NDIS_STATUS_RFAILURE If the object was not found. --*/ { RM_STATUS Status = NDIS_STATUS_FAILURE; BOOLEAN fUnlockOutOfOrder = FALSE; PRM_OBJECT_HEADER pOwningObject = pGroup->pOwningObject; PRM_OBJECT_HEADER pObject; #if DBG KIRQL EntryIrql = KeGetCurrentIrql(); #endif // DBG ENTER("RmLookupObjectInGroup", 0xd2cd6379) ASSERT(pOwningObject!=NULL); // OBSOLETE -- see comments above: ASSERT(pfCreated==NULL || (Flags&RM_LOCKED)); if (pfCreated != NULL) *pfCreated = FALSE; NdisAcquireSpinLock(&pGroup->OsLock); do { BOOLEAN fFound; PRM_HASH_LINK *ppLink = NULL; if (!RMISALLOCATED(pGroup->pOwningObject)) break; if (pGroup->fEnabled != TRUE) break; fFound = RmLookupHashTable( &pGroup->HashTable, &ppLink, pvKey ); if (fFound) { if (Flags & RM_NEW) { // Caller wanted us to created a new object, but the object already // exists, so we fail... // // TODO: return appropriate error code. // break; } // Go from hash-link to object. // TODO: once HashLink goes away, need some other way to get // to the object. // pObject = CONTAINING_RECORD(*ppLink, RM_OBJECT_HEADER, HashLink); ASSERT(pObject->pStaticInfo == pGroup->pStaticInfo); } else { if (!(Flags & RM_CREATE)) { // Couldn't find it, and caller doesn't want us to create one, so // we fail... break; } // Create object... // ASSERTEX(pGroup->pStaticInfo->pfnCreate!=NULL, pGroup); pObject = pGroup->pStaticInfo->pfnCreate( pOwningObject, pvCreateParams, pSR ); if (pObject == NULL) { Status = NDIS_STATUS_RESOURCES; break; } TR_INFO(( "Created 0x%p (%s) in Group 0x%p (%s)\n", pObject, pObject->szDescription, pGroup, pGroup->szDescription )); ASSERTEX(RMISALLOCATED(pObject), pObject); // Now enter it into the hash table. // RmAddHashItem( &pGroup->HashTable, ppLink, &pObject->HashLink, pvKey ); if (pfCreated != NULL) { *pfCreated = TRUE; } } RmTmpReferenceObject(pObject, pSR); Status = NDIS_STATUS_SUCCESS; } while(FALSE); NdisReleaseSpinLock(&pGroup->OsLock); // Do we need to look the object before returning it // to the user if ((!FAIL(Status)) && (Flags & RM_LOCKED)) { RmWriteLockObject( pObject, #if RM_EXTRA_CHECKING 0x6197fdda, #endif //RM_EXTRA_CHECKING pSR ); if (!RMISALLOCATED(pObject)) { // We don't allow this... RmUnlockObject( pObject, pSR ); } } if (FAIL(Status)) { *ppObject = NULL; } else { *ppObject = pObject; } #if DBG { KIRQL ExitIrql = KeGetCurrentIrql(); TR_VERB(("Exiting. EntryIrql=%lu, ExitIrql = %lu\n", EntryIrql, ExitIrql)); } #endif //DBG return Status; } RM_STATUS RmGetNextObjectInGroup( IN PRM_GROUP pGroup, IN PRM_OBJECT_HEADER pCurrentObject, // OPTIONAL OUT PRM_OBJECT_HEADER * ppNextObject, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Get the 1st object in group (if pCurrentObject == NULL), or the object "after" pCurrentObject (if pCurrentObject != NULL). The definition of "after" is hidden -- the only guarantee is if this function is 1st called with NULL pCurrentObject and subsequently with pCurrentObject set to the value previously returned in ppNextObject, until the function returns NDIS_STATUS_FAILURE, all objects in the group will be returned once and only once. This guarantee is only valid if no objects are added or removed during the enumeration process. On success, the "next" object is tmpref'd a pointer to it is saved in *ppNextObject. Arguments: pGroup - The group pCurrentObject - (OPTIONAL) An object in the group. ppNextObject - Place to return the the object "after" pCurrentObject (see RoutineDescription for details.) Return Value: NDIS_STATUS_SUCCESS if we could find a "next" object. NDIS_STATUS_FAILURE otherwise --*/ { RM_STATUS Status = NDIS_STATUS_FAILURE; PRM_OBJECT_HEADER pOwningObject = pGroup->pOwningObject; PRM_OBJECT_HEADER pObject; ENTER("RmGetNextObjectInGroup", 0x11523db7) ASSERT(pOwningObject!=NULL); NdisAcquireSpinLock(&pGroup->OsLock); do { BOOLEAN fFound; PRM_HASH_LINK pLink = NULL; PRM_HASH_LINK pCurrentLink = NULL; if (!RMISALLOCATED(pGroup->pOwningObject)) break; if (pGroup->fEnabled != TRUE) break; if (pCurrentObject != NULL) { pCurrentLink = &pCurrentObject->HashLink; } fFound = RmNextHashTableItem( &pGroup->HashTable, pCurrentLink, // pCurrentLink &pLink // pNextLink ); if (fFound) { // Go from hash-link to object. // TODO: once HashLink goes away, need some other way to get // to the object. // pObject = CONTAINING_RECORD(pLink, RM_OBJECT_HEADER, HashLink); ASSERT(pObject->pStaticInfo == pGroup->pStaticInfo); } else { // Couldn't find one. // we fail... break; } RmTmpReferenceObject(pObject, pSR); Status = NDIS_STATUS_SUCCESS; } while(FALSE); NdisReleaseSpinLock(&pGroup->OsLock); if (FAIL(Status)) { *ppNextObject = NULL; } else { *ppNextObject = pObject; } return Status; } VOID RmFreeObjectInGroup( IN PRM_GROUP pGroup, IN PRM_OBJECT_HEADER pObject, IN struct _RM_TASK *pTask, OPTIONAL // Unused. TODO: remove this. IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Remove object pObject from group pGroup and deallocate pObject. --*/ { ENTER("RmFreeObjectInGroup", 0xd2cd6379) PRM_OBJECT_HEADER pOwningObject = pGroup->pOwningObject; ASSERTEX(pOwningObject!=NULL, pGroup); ASSERTEX(pTask==NULL, pGroup); NdisAcquireSpinLock(&pGroup->OsLock); // TODO: what if at this time, someone else is doing FreeAllObjects in Group? // TR_INFO(( "Freeing 0x%p (%s) in Group 0x%p (%s)\n", pObject, pObject->szDescription, pGroup, pGroup->szDescription )); ASSERTEX(RMISALLOCATED(pObject), pObject); RmRemoveHashItem( &pGroup->HashTable, &pObject->HashLink ); NdisReleaseSpinLock(&pGroup->OsLock); // Deallocate the object. // RmDeallocateObject( pObject, pSR ); EXIT() } VOID RmFreeAllObjectsInGroup( IN PRM_GROUP pGroup, IN struct _RM_TASK *pTask, OPTIONAL // Unused. TODO: remove this. IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Remove and deallocate all object in pGroup. --*/ { PRM_HASH_LINK *ppLink, *ppLinkEnd; NdisAcquireSpinLock(&pGroup->OsLock); if (pGroup->fEnabled) { pGroup->fEnabled = FALSE; } else { NdisReleaseSpinLock(&pGroup->OsLock); return; // EARLY RETURN } // // With fEnabled set to FALSE by us, we expect the following: // (a) pHashTable->pTable is going to stay the same size. // (b) No items are going to be added or removed by anyone else. // ppLink = pGroup->HashTable.pTable; ppLinkEnd = ppLink + pGroup->HashTable.TableLength; for ( ; ppLink < ppLinkEnd; ppLink++) { while (*ppLink != NULL) { PRM_HASH_LINK pLink = *ppLink; PRM_OBJECT_HEADER pObj; // Remove it from the bucket list. // *ppLink = pLink->pNext; pLink->pNext = NULL; pGroup->HashTable.NumItems--; NdisReleaseSpinLock(&pGroup->OsLock); pObj = CONTAINING_RECORD(pLink, RM_OBJECT_HEADER, HashLink); ASSERT(pObj->pStaticInfo == pGroup->pStaticInfo); // Deallocate the object. // RmDeallocateObject( pObj, pSR ); NdisAcquireSpinLock(&pGroup->OsLock); } } NdisReleaseSpinLock(&pGroup->OsLock); } VOID RmUnloadAllObjectsInGroup( IN PRM_GROUP pGroup, PFN_RM_TASK_ALLOCATOR pfnUnloadTaskAllocator, PFN_RM_TASK_HANDLER pfnUnloadTaskHandler, PVOID pvUserParam, IN struct _RM_TASK *pTask, OPTIONAL IN UINT uTaskPendCode, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Stops new objects from being added and unloads(see below) all objects currently in the group. "Unload" consists of allocating and starting a pfnUnloadTaskHask task on each object. The unload task is responsible for removing and deallocating the object from the group. If pTask if non-NULL, it will be resumed on completion of the unload. Otherwise, this function will BLOCK until the unload is complete. Arguments: pGroup - Group to unload. pfnUnloadTaskAllocator - Use to allocate the object-unload tasks. pfnTaskAllocator - Function used to allocate the unload task. pfnUnloadTaskHandler - The handler of the unload task pvUserParam - Task creation user-param. WARNING: this param must be valid for the duration of the unload process, not just until this function returns. Of course, if pTask is NULL, the two cases are equivalent. pTask - (OPTIONAL) Task to resume when unload is complete. If NULL, this function will block until the unload is complete. uTaskPendCode - (OPTIONAL) PendCode to use when resuming pTask. --*/ { PRM_TASK pUnloadTask; NDIS_STATUS Status; NdisAcquireSpinLock(&pGroup->OsLock); // // We don't check if there is already an unload task active for this group. // Instead we go ahead and allocate and start an unload task. This latter // task will pend on the already running unload task if there is on. // // Allocate a private task to coordinate the unloading of all the objects. // Status = rmAllocatePrivateTask( pGroup->pOwningObject, rmTaskUnloadGroup, 0, "Task:UnloadAllObjectsInGroup", &pUnloadTask, pSR ); if (FAIL(Status)) { // // Ouch -- ugly failure... // ASSERT(FALSE); NdisReleaseSpinLock(&pGroup->OsLock); } else { TASK_UNLOADGROUP *pUGTask = (TASK_UNLOADGROUP *) pUnloadTask; pUGTask->pGroup = pGroup; pUGTask->pfnTaskUnloadObjectHandler = pfnUnloadTaskHandler; pUGTask->pfnUnloadTaskAllocator = pfnUnloadTaskAllocator; if (pTask == NULL) { // Set up an event which we'll wait on. The event will be signaled // by pUnloadTask when it completes. // NdisInitializeEvent(&pUGTask->BlockEvent); pUGTask->fUseEvent = TRUE; // Tmpref it so pUnloadTask will stay around even afer it's // completed -- because we wait on the event that's actually // located in the task memory. // RmTmpReferenceObject(&pUnloadTask->Hdr, pSR); } NdisReleaseSpinLock(&pGroup->OsLock); if (pTask != NULL) { RmPendTaskOnOtherTask( pTask, uTaskPendCode, pUnloadTask, pSR ); } Status = RmStartTask(pUnloadTask, 0, pSR); if (pTask == NULL) { NdisWaitEvent(&pUGTask->BlockEvent, 0); RmTmpDereferenceObject(&pUnloadTask->Hdr, pSR); } } } VOID RmEnableGroup( IN PRM_GROUP pGroup, IN PRM_STACK_RECORD pSR ) /*++ TODO: need better name for this. Routine Description: Enables items to be added to a group. This function is typically called with a group which has completed RmUnloadAllObjectsFromGroup or RmFreeAllObjectsInGroup. On return from this call items may once more be added to this group. This call must only be called after UnloadAllObjectsInGroup or RmFreeAllObjectsInGroup have completed (synchronously or asynchronously). If there are items in in group or there is an unload task associated with the group at the time this function is called, the group is NOT reinited and the DBG version will assert. This function and may be called with an already enabled group, provided the condition above is met (no items in group, no unload task). --*/ { NdisAcquireSpinLock(&pGroup->OsLock); if ( pGroup->pUnloadTask == NULL && pGroup->HashTable.NumItems == 0) { pGroup->fEnabled = TRUE; } else { ASSERT("invalid state."); } NdisReleaseSpinLock(&pGroup->OsLock); } VOID RmInitializeTask( IN PRM_TASK pTask, IN PRM_OBJECT_HEADER pParentObject, IN PFN_RM_TASK_HANDLER pfnHandler, IN PRM_STATIC_OBJECT_INFO pStaticInfo, OPTIONAL IN const char * szDescription, OPTIONAL IN UINT Timeout, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Initialize the specified task. The task is tempref'd. It is the responsibility of the caller to de-ref it when done. Typically this is implicitly done by calling RmStartTask. Arguments: pTask - points to unitialized memory to hold the task. pParentObject - will be the parent of the task. pfnHandler - task's handler function. pStaticInfo - (OPTIONAL) Static information about the task. szDescription - (debug only, OPTIONAL) description of the task Timeout - unused --*/ { ASSERT(!Timeout); // TODO: Timeouts unimplemented. NdisZeroMemory(pTask, sizeof(*pTask)); RmInitializeHeader( pParentObject, &pTask->Hdr, MTAG_TASK, pParentObject->pLock, (pStaticInfo) ? pStaticInfo : &RmTask_StaticInfo, szDescription, pSR ); pTask->pfnHandler = pfnHandler; SET_RM_TASK_STATE(pTask, RMTSKSTATE_IDLE); InitializeListHead(&pTask->listTasksPendingOnMe); RmTmpReferenceObject(&pTask->Hdr, pSR); } VOID RmAbortTask( IN PRM_TASK pTask, IN PRM_STACK_RECORD pSR ) { ASSERT(!"Unimplemented"); } RM_STATUS RmStartTask( IN PRM_TASK pTask, IN UINT_PTR UserParam, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Start the specified task. NO locks should be held on entry and none are held on exit. pTask is expected to have a tmp-ref which is deref'd here. The task is automatically deallocated on completion (either synchronous or asynchronous completion, either successful or failed completion). Unless the caller is explicitly added a reference to pTask before calling this function, the caller should not assume that pTask is still valid on return from this function. Arguments: pTask - points to the task to be started. UserParam - opaque value passed to the task handler with the RM_TASKOP_START message. --*/ { ENTER("RmStartTask", 0xf80502d5) NDIS_STATUS Status; RM_ASSERT_NOLOCKS(pSR); RMPRIVATELOCK(&pTask->Hdr, pSR); if (!CHECK_RM_TASK_STATE(pTask, RMTSKSTATE_IDLE)) { ASSERTEX(!"Invalid state", pTask); RMPRIVATEUNLOCK(&pTask->Hdr, pSR); Status = NDIS_STATUS_FAILURE; } else { if (!RMISALLOCATED(pTask->Hdr.pParentObject)) { // // TODO: consider not calling the handler if the parent object is // deallocated, but that may be confusing. // Consider not allowing children to be linked to an object // (RmInitializeHeader returns failure) if the parent object is // deallocated. // TR_WARN(( "Starting task 0x%p (%s) with DEALLOCATED parent 0x%p (%s).\n", pTask, pTask->Hdr.szDescription, pTask->Hdr.pParentObject, pTask->Hdr.pParentObject->szDescription )); } SET_RM_TASK_STATE(pTask, RMTSKSTATE_STARTING); RMPRIVATEUNLOCK(&pTask->Hdr, pSR); TR_INFO(( "STARTING Task 0x%p (%s); UserParam = 0x%lx\n", pTask, pTask->Hdr.szDescription, UserParam )); Status = pTask->pfnHandler( pTask, RM_TASKOP_START, UserParam, pSR ); RM_ASSERT_NOLOCKS(pSR); RMPRIVATELOCK(&pTask->Hdr, pSR); switch(GET_RM_TASK_STATE(pTask)) { case RMTSKSTATE_STARTING: // This task is completing synchronously. // ASSERT(Status != NDIS_STATUS_PENDING); SET_RM_TASK_STATE(pTask, RMTSKSTATE_ENDING); RMPRIVATEUNLOCK(&pTask->Hdr, pSR); rmEndTask(pTask, Status, pSR); RmDeallocateObject(&pTask->Hdr, pSR); break; case RMTSKSTATE_PENDING: ASSERTEX(Status == NDIS_STATUS_PENDING, pTask); RMPRIVATEUNLOCK(&pTask->Hdr, pSR); break; case RMTSKSTATE_ENDING: // This task is completing synchronously and the RM_TASKOP_END // notification has already been sent. // // ??? ASSERT(Status != NDIS_STATUS_PENDING); RMPRIVATEUNLOCK(&pTask->Hdr, pSR); // ??? RmDeallocateObject(&pTask->Hdr, pSR); break; default: ASSERTEX(FALSE, pTask); // Fall through ... case RMTSKSTATE_ACTIVE: // This can happen if the task is in the process of being resumed // in the context of some other thread. Nothing to do here... // (This actually happens sometimes on a MP machine). // RMPRIVATEUNLOCK(&pTask->Hdr, pSR); break; } } // Remove the tmp ref added when the task was allocated. // RmTmpDereferenceObject( &pTask->Hdr, pSR ); RM_ASSERT_NOLOCKS(pSR); EXIT() return Status; } VOID RmDbgDumpTask( IN PRM_TASK pTask, IN PRM_STACK_RECORD pSR ) { } RM_STATUS RmSuspendTask( IN PRM_TASK pTask, IN UINT SuspendContext, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Suspends the specified task. RmSuspendTask is always called in the context of a task handler. pTask is may be locked on entry -- we don't care. Arguments: pTask - task to be suspended. SuspendContext - context to be presented to the task's handler when the task is subsequently resumed. Specifically, this context may be accessed using the RM_PEND_CODE macro, when the task's handler is called with code RM_TASKOP_PENDCOMPLETE. --*/ { ENTER("RmSuspendTask", 0xd80fdc00) NDIS_STATUS Status; // RM_ASSERT_NOLOCKS(pSR); RMPRIVATELOCK(&pTask->Hdr, pSR); TR_INFO(( "SUSPENDING Task 0x%p (%s); SuspendContext = 0x%lx\n", pTask, pTask->Hdr.szDescription, SuspendContext )); if ( !CHECK_RM_TASK_STATE(pTask, RMTSKSTATE_STARTING) && !CHECK_RM_TASK_STATE(pTask, RMTSKSTATE_ACTIVE)) { ASSERTEX(!"Invalid state", pTask); Status = NDIS_STATUS_FAILURE; } else { SET_RM_TASK_STATE(pTask, RMTSKSTATE_PENDING); pTask->SuspendContext = SuspendContext; Status = NDIS_STATUS_SUCCESS; } RMPRIVATEUNLOCK(&pTask->Hdr, pSR); // RM_ASSERT_NOLOCKS(pSR); EXIT() return Status; } VOID RmUnsuspendTask( IN PRM_TASK pTask, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Undoes the effect of a previous call to RmSuspendTask. Task MUST be in the pending state and MUST NOT be pending on another task. Debug version will ASSERT if above conditions are not met. RmUnsuspendTask is always called in the context of a task handler. pTask is may be locked on entry -- we don't care. Arguments: pTask - task to be suspended. --*/ { ENTER("RmUnsuspendTask", 0xcf713639) RMPRIVATELOCK(&pTask->Hdr, pSR); TR_INFO(( "UN-SUSPENDING Task 0x%p (%s). SuspendContext = 0x%x\n", pTask, pTask->Hdr.szDescription, pTask->SuspendContext )); ASSERT(CHECK_RM_TASK_STATE(pTask, RMTSKSTATE_PENDING)); ASSERT(pTask->pTaskIAmPendingOn == NULL); SET_RM_TASK_STATE(pTask, RMTSKSTATE_ACTIVE); pTask->SuspendContext = 0; RMPRIVATEUNLOCK(&pTask->Hdr, pSR); EXIT() } VOID RmResumeTask( IN PRM_TASK pTask, IN UINT_PTR SuspendCompletionParam, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Resume a previously-suspended task. No locks held on entry or exit. SuspendCompletionParam is user-defined, and must be agreed upon between the caller of RmUnpendTask and the task that's being unpended. The Task's handler is ALWAYS called in the context of the caller of RmUnpendTask. So it is ok for the caller to declare a structure on the stack and pass a pointer to it as SuspendCompletionParam. WARNING: pTask could well be invalid (deallocate) by the time we return from this function. The caller is responsible for tmprefing pTask if it needs to access after return from this function. Arguments: pTask - task to be resumed. SuspendCompletionParam - arbitrary value that is passed on to the task's handler as "UserParan" when the handler is called with code RM_TASKOP_PENDCOMPLETE. --*/ { ENTER("RmResumeTask", 0xd261f3c6) NDIS_STATUS Status; RM_ASSERT_NOLOCKS(pSR); RMPRIVATELOCK(&pTask->Hdr, pSR); TR_INFO(( "RESUMING Task 0x%p (%s); SuspendCompletionParam = 0x%lx\n", pTask, pTask->Hdr.szDescription, SuspendCompletionParam )); if (!CHECK_RM_TASK_STATE(pTask, RMTSKSTATE_PENDING)) { RMPRIVATEUNLOCK(&pTask->Hdr, pSR); ASSERTEX(!"Invalid state", pTask); } else { // Add tmp ref, because we need to look at pTask after the return // from calling pfnHandler. // RmTmpReferenceObject(&pTask->Hdr, pSR); SET_RM_TASK_STATE(pTask, RMTSKSTATE_ACTIVE); RMPRIVATEUNLOCK(&pTask->Hdr, pSR); Status = pTask->pfnHandler( pTask, RM_TASKOP_PENDCOMPLETE, SuspendCompletionParam, pSR ); RM_ASSERT_NOLOCKS(pSR); RMPRIVATELOCK(&pTask->Hdr, pSR); switch(GET_RM_TASK_STATE(pTask)) { case RMTSKSTATE_ACTIVE: // This task is completing here (maybe) // if (Status != NDIS_STATUS_PENDING) { SET_RM_TASK_STATE(pTask, RMTSKSTATE_ENDING); RMPRIVATEUNLOCK(&pTask->Hdr, pSR); rmEndTask(pTask, Status, pSR); RmDeallocateObject(&pTask->Hdr, pSR); } else { // It could be returning pending, but the state could // by now be active because it was completed elsewhere. // ASSERT(Status != NDIS_STATUS_PENDING); RMPRIVATEUNLOCK(&pTask->Hdr, pSR); } break; case RMTSKSTATE_PENDING: ASSERTEX(Status == NDIS_STATUS_PENDING, pTask); RMPRIVATEUNLOCK(&pTask->Hdr, pSR); break; case RMTSKSTATE_ENDING: // This task is completing synchronously and the RM_TASKOP_END // notification has already been sent. // RMPRIVATEUNLOCK(&pTask->Hdr, pSR); break; default: ASSERTEX(FALSE, pTask); RMPRIVATEUNLOCK(&pTask->Hdr, pSR); } // Remove tmpref added above. pTask may well go away now... // RmTmpDereferenceObject(&pTask->Hdr, pSR); } RM_ASSERT_NOLOCKS(pSR); EXIT() } VOID RmResumeTaskAsync( IN PRM_TASK pTask, IN UINT_PTR SuspendCompletionParam, IN OS_WORK_ITEM * pOsWorkItem, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Similar to RmResumeTask, except that the task is resumed in the context of a work-item thread. Arguments: pTask - see RmResumeTask SuspendCompletionParam - see RmResumeTask pOsWorkItem - caller supplied UNitialized work item (must stay around until the task is resumed). Typically this will be located within the user-specific portion of pTask --*/ { NDIS_STATUS Status; RM_ASSERT_NOLOCKS(pSR); #if RM_EXTRA_CHECKING // This may seem paranoid, but is such a powerful check that it's worth it. // RmDbgAddAssociation( 0x33d63ece, // Location ID &pTask->Hdr, // pObject (UINT_PTR) SuspendCompletionParam, // Instance1 (UINT_PTR) pOsWorkItem, // Instance2 RM_PRIVATE_ASSOC_RESUME_TASK_ASYNC, // AssociationID szASSOCFORMAT_RESUME_TASK_ASYNC, // szAssociationFormat pSR ); #endif // RM_EXTRA_CHECKING // We don't need to grab the private lock to set this, because only one // entity can call RmResumeTaskAsync. Note that we also ensure things are clean // (in the debug case) by the association added above. // pTask->AsyncCompletionParam = SuspendCompletionParam; NdisInitializeWorkItem( pOsWorkItem, rmWorkItemHandler_ResumeTaskAsync, pTask ); Status = NdisScheduleWorkItem(pOsWorkItem); if (FAIL(Status)) { ASSERT(!"NdisStatusWorkItem failed."); // It so happens that NdisScheudleWorkItem (current implementation // doesn't fail. Nevertheless, we do the best we can and actually // resume the task. If the caller was at dpc level and was expecting // the task to resume at passive, they're out of luck. // RmResumeTask(pTask, SuspendCompletionParam, pSR); } } VOID RmResumeTaskDelayed( IN PRM_TASK pTask, IN UINT_PTR SuspendCompletionParam, IN ULONG MsDelay, IN OS_TIMER * pOsTimer, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Similar to RmResumeTask, except that the task is resumed in the context of a os timer handler which is set to fire after MsDelay milliseconds from the time RmResumeTaskDelayed is called. EXCEPTION: if someone has previously called RmResumeDelayedTaskNow, this task could be resumed in the context of this function call itself. Abort implementation notes: see notes.txt 07/14/1999 entry. Arguments: pTask - see RmResumeTask SuspendCompletionParam - see RmResumeTask pOsTimer - caller supplied UNitialized timer (must stay around until the task is resumed). Typically this will be located within the user-specific portion of pTask. --*/ { NDIS_STATUS Status; RM_ASSERT_NOLOCKS(pSR); #if RM_EXTRA_CHECKING // This may seem paranoid, but is such a powerful check that it's worth it. // RmDbgAddAssociation( 0x33d63ece, // Location ID &pTask->Hdr, // pObject (UINT_PTR) SuspendCompletionParam, // Instance1 (UINT_PTR) NULL, // Instance2 RM_PRIVATE_ASSOC_RESUME_TASK_DELAYED, // AssociationID szASSOCFORMAT_RESUME_TASK_DELAYED, // szAssociationFormat pSR ); #endif // RM_EXTRA_CHECKING // Ddk states that it's best to call this function at passive level. // NdisInitializeTimer( pOsTimer, rmTimerHandler_ResumeTaskDelayed, pTask ); RMPRIVATELOCK(&pTask->Hdr, pSR); // The task-del state should NOT be "delayed" // ASSERT(RM_CHECK_STATE(pTask, RMTSKDELSTATE_MASK, 0)); pTask->AsyncCompletionParam = SuspendCompletionParam; RM_SET_STATE(pTask, RMTSKDELSTATE_MASK, RMTSKDELSTATE_DELAYED); if (RM_CHECK_STATE(pTask, RMTSKABORTSTATE_MASK, RMTSKABORTSTATE_ABORT_DELAY)) { // Oops, the delay has been aborted -- we call the tick handler now! // RMPRIVATEUNLOCK(&pTask->Hdr, pSR); rmTimerHandler_ResumeTaskDelayed( NULL, // SystemSpecific1, pTask, // FunctionContext, NULL, // SystemSpecific2, NULL // SystemSpecific3 ); } else { // // Not currently aborting, let's set the timer. // NdisSetTimer(pOsTimer, MsDelay); // Very important to unlock the private lock AFTER calling set timer, // other wise someone could call RmResumeDelayedTaskNow BEFORE we call // NdisSetTimer, in which we would not end up aborting the delayed task. // RMPRIVATEUNLOCK(&pTask->Hdr, pSR); } } VOID RmResumeDelayedTaskNow( IN PRM_TASK pTask, IN OS_TIMER * pOsTimer, OUT PUINT pTaskResumed, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Cut's short the delay and resumes the task immediately. Implementation notes: see notes.txt 07/14/1999 entry. Arguments: pTask - see RmResumeTask pOsTimer - caller supplied initialized timer (must stay around until the task is resumed). Typically this will be located within the user-specific portion of pTask. pTaskResumed - Points to a caller-supplied variable. RmResumeDelayedTask sets this variable to TRUE if the task was resumed as a consequence of this call, or to FALSE if the task was resumed due to some other reason. --*/ { UINT_PTR CompletionParam = pTask->AsyncCompletionParam; *pTaskResumed = FALSE; ASSERTEX(RMISALLOCATED(&pTask->Hdr), pTask); RMPRIVATELOCK(&pTask->Hdr, pSR); RM_SET_STATE(pTask, RMTSKABORTSTATE_MASK, RMTSKABORTSTATE_ABORT_DELAY); if (RM_CHECK_STATE(pTask, RMTSKDELSTATE_MASK, RMTSKDELSTATE_DELAYED)) { BOOLEAN TimerCanceled = FALSE; // // The task is actually delayed. Let's go ahead and cancel the timer // and resume the task now (which we do indirectly by calling // the timer handler ourselves). // NdisCancelTimer(pOsTimer, &TimerCanceled); if (TimerCanceled) { // // The timer was actually canceled -- so we call the timer handler // ourselves. // RMPRIVATEUNLOCK(&pTask->Hdr, pSR); rmTimerHandler_ResumeTaskDelayed( NULL, // SystemSpecific1, pTask, // FunctionContext, NULL, // SystemSpecific2, NULL // SystemSpecific3 ); *pTaskResumed = TRUE; } else { // // Hmm -- the timer is not enabled. This is either because // the timer handler has just been called (and not yet cleared // the "DELAY" state) OR someone has previously called // RmResumeDelayedTaskNow. // // // Nothing to do. // RMPRIVATEUNLOCK(&pTask->Hdr, pSR); } } else { // // The task state is not delayed -- so we just set the abort state // and go away. // RMPRIVATEUNLOCK(&pTask->Hdr, pSR); } } RM_STATUS RmPendTaskOnOtherTask( IN PRM_TASK pTask, IN UINT SuspendContext, IN PRM_TASK pOtherTask, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Pend task pTask on task pOtherTask. Note: RmPendTaskOnOtherTask will cause pTask's pend operation to be completed in the context of this call itself, if pOtherTask is already in the completed state. 03/26/1999 -- see RmPendTaskOnOtherTaskV2, and also 03/26/1999 notes.txt entry "Some proposed ..." Arguments: pTask - task to be suspended. SuspendContext - Context associated with the suspend (see RmSuspendTask for details). pOtherTask - task that pTask is to pend on. Return Value: NDIS_STATUS_SUCCESS on success. NDIS_STATUS_FAILURE on failure (typically because pTask is not in as position to be suspended.) --*/ { ENTER("RmPendTaskOnOtherTask", 0x0416873e) NDIS_STATUS Status = NDIS_STATUS_FAILURE; BOOLEAN fResumeTask = FALSE; RM_ASSERT_NOLOCKS(pSR); TR_INFO(( "PENDING Task 0x%p (%s) on Task 0x%p (%s). SuspendCompletionParam = 0x%lx\n", pTask, pTask->Hdr.szDescription, pOtherTask, pOtherTask->Hdr.szDescription, SuspendContext )); // // WARNING: we break the locking rules here by getting the lock on // both pTask and pOtherTask. // TODO: consider acquiring them in order of increasing numerical value. // ASSERT(pTask != pOtherTask); NdisAcquireSpinLock(&(pOtherTask->Hdr.RmPrivateLock.OsLock)); NdisAcquireSpinLock(&(pTask->Hdr.RmPrivateLock.OsLock)); do { // Break if we can't pend pTask on pOtherTask. // { if ( !CHECK_RM_TASK_STATE(pTask, RMTSKSTATE_STARTING) && !CHECK_RM_TASK_STATE(pTask, RMTSKSTATE_ACTIVE)) { ASSERTEX(!"Invalid state", pTask); break; } // Non-NULL pTaskIAmPendingOn implies that pTask is already pending on // some other task! // if (pTask->pTaskIAmPendingOn != NULL) { ASSERTEX(!"Invalid state", pTask); break; } } Status = NDIS_STATUS_SUCCESS; SET_RM_TASK_STATE(pTask, RMTSKSTATE_PENDING); pTask->SuspendContext = SuspendContext; if (CHECK_RM_TASK_STATE(pOtherTask, RMTSKSTATE_ENDING)) { // // Other task is done -- so we resume pTask before returning... // fResumeTask = TRUE; break; } // // pOtherTask is not ended -- add pTask to the list of tasks pending // on pOtherTask. // pTask->pTaskIAmPendingOn = pOtherTask; #if RM_EXTRA_CHECKING RmLinkObjectsEx( &pTask->Hdr, &pOtherTask->Hdr, 0x77c488ca, RM_PRIVATE_ASSOC_LINK_TASKPENDINGON, szASSOCFORMAT_LINK_TASKPENDINGON, RM_PRIVATE_ASSOC_LINK_TASKBLOCKS, szASSOCFORMAT_LINK_TASKBLOCKS, pSR ); #else // !RM_EXTRA_CHECKING RmLinkObjects(&pTask->Hdr, &pOtherTask->Hdr, pSR); #endif // !RM_EXTRA_CHECKING ASSERTEX(pTask->linkFellowPendingTasks.Blink == NULL, pTask); ASSERTEX(pTask->linkFellowPendingTasks.Flink == NULL, pTask); InsertHeadList( &pOtherTask->listTasksPendingOnMe, &pTask->linkFellowPendingTasks ); } while(FALSE); NdisReleaseSpinLock(&(pTask->Hdr.RmPrivateLock.OsLock)); NdisReleaseSpinLock(&(pOtherTask->Hdr.RmPrivateLock.OsLock)); if (fResumeTask) { RmResumeTask( pTask, NDIS_STATUS_SUCCESS, // SuspendCompletionParam. TODO: put real code. pSR ); } RM_ASSERT_NOLOCKS(pSR); EXIT() return Status; } RM_STATUS RmPendOnOtherTaskV2( IN PRM_TASK pTask, IN UINT SuspendContext, IN PRM_TASK pOtherTask, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: if pOtherTask is not complete, Pend task pTask on task pOtherTask and return NDIS_STATUS_PENDING. However, if pOtherTask is already complete, then don't pend and instead return NDIS_STATUS_SUCCESS. See 03/26/1999 notes.txt entry "Some proposed ...". This function is currently used only by rmTaskUnloadGroup, to avoid the problem describted in the above-referenced notes.txt entry. TODO: Eventually get rid of RmPendTaskOnOtherTask. Arguments: See RmPendTaskOnOtherTask Return Value: NDIS_STATUS_PENDING if pTask is pending on pOtherTask NDIS_STATUS_SUCCESS if pOtherTask is complete. NDIS_STATUS_FAILURE if there was some failure (typically pTask is not in a position to be pended.) --*/ { ENTER("RmPendTaskOnOtherTaskV2", 0x0e7d1b89) NDIS_STATUS Status = NDIS_STATUS_FAILURE; RM_ASSERT_NOLOCKS(pSR); TR_INFO(( "PENDING(V2) Task 0x%p (%s) on Task 0x%p (%s). SuspendCompletionParam = 0x%lx\n", pTask, pTask->Hdr.szDescription, pOtherTask, pOtherTask->Hdr.szDescription, SuspendContext )); // This is not a useless assert -- I'e had a bug elsewhere which caused this // assert to get hit. // ASSERT(pTask != pOtherTask); // // WARNING: we break the locking rules here by getting the lock on // both pTask and pOtherTask. // TODO: consider acquiring them in order of increasing numerical value. // NdisAcquireSpinLock(&(pOtherTask->Hdr.RmPrivateLock.OsLock)); NdisAcquireSpinLock(&(pTask->Hdr.RmPrivateLock.OsLock)); do { // Break if we can't pend pTask on pOtherTask. // { if ( !CHECK_RM_TASK_STATE(pTask, RMTSKSTATE_STARTING) && !CHECK_RM_TASK_STATE(pTask, RMTSKSTATE_ACTIVE)) { ASSERTEX(!"Invalid state", pTask); break; } // Non-NULL pTaskIAmPendingOn implies that pTask is already pending on // some other task! // if (pTask->pTaskIAmPendingOn != NULL) { ASSERTEX(!"Invalid state", pTask); break; } } if (CHECK_RM_TASK_STATE(pOtherTask, RMTSKSTATE_ENDING)) { // // Other task is done -- so we simply return success... // Status = NDIS_STATUS_SUCCESS; break; } // // pOtherTask is not ended -- set pTask state to pending, and // add it to the list of tasks pending on pOtherTask. // SET_RM_TASK_STATE(pTask, RMTSKSTATE_PENDING); pTask->SuspendContext = SuspendContext; pTask->pTaskIAmPendingOn = pOtherTask; #if RM_EXTRA_CHECKING RmLinkObjectsEx( &pTask->Hdr, &pOtherTask->Hdr, 0x77c488ca, RM_PRIVATE_ASSOC_LINK_TASKPENDINGON, szASSOCFORMAT_LINK_TASKPENDINGON, RM_PRIVATE_ASSOC_LINK_TASKBLOCKS, szASSOCFORMAT_LINK_TASKBLOCKS, pSR ); #else // !RM_EXTRA_CHECKING RmLinkObjects(&pTask->Hdr, &pOtherTask->Hdr, pSR); #endif // !RM_EXTRA_CHECKING ASSERTEX(pTask->linkFellowPendingTasks.Blink == NULL, pTask); ASSERTEX(pTask->linkFellowPendingTasks.Flink == NULL, pTask); InsertHeadList( &pOtherTask->listTasksPendingOnMe, &pTask->linkFellowPendingTasks ); Status = NDIS_STATUS_PENDING; } while(FALSE); NdisReleaseSpinLock(&(pTask->Hdr.RmPrivateLock.OsLock)); NdisReleaseSpinLock(&(pOtherTask->Hdr.RmPrivateLock.OsLock)); RM_ASSERT_NOLOCKS(pSR); EXIT() return Status; } VOID RmCancelPendOnOtherTask( IN PRM_TASK pTask, IN PRM_TASK pOtherTask, IN UINT_PTR UserParam, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Resume task pTask which is currently pending on pOtherTask. Since no locks are held, pOtherTask needs to be specified, to make sure that pTask is indeed pending on pOtherTask before canceling the pend. If pTask is indeed pending on pOtherTask, this function will cause the completion of the pend status with the specified user param. Has no effect if the task is not pending. Arguments: pTask - Task to be "unpended" pOtherTask - Task pTask is currently pending on. UserParam - Passed to pTask's handler if and when pTask is resumed. --*/ { ENTER("RmCancelPendOnOtherTask", 0x6e113266) NDIS_STATUS Status = NDIS_STATUS_FAILURE; BOOLEAN fResumeTask = FALSE; RM_ASSERT_NOLOCKS(pSR); // // WARNING: we break the locking rules here by getting the lock on // both pTask and pOtherTask. // TODO: consider acquiring them in order of increasing numerical value. // TR_INFO(( "CANCEL PEND of Task 0x%p (%s) on other Task 0x%p (%s); UserParam = 0x%lx\n", pTask, pTask->Hdr.szDescription, pOtherTask, pOtherTask->Hdr.szDescription, UserParam )); // With pTask locked, tmp ref the task it is pending on, if any... // { NdisAcquireSpinLock(&(pTask->Hdr.RmPrivateLock.OsLock)); if (pOtherTask == pTask->pTaskIAmPendingOn) { RmTmpReferenceObject(&(pOtherTask->Hdr), pSR); } else { // Oops -- pTask is not pending on pOtherTask ... // pOtherTask = NULL; } NdisReleaseSpinLock(&(pTask->Hdr.RmPrivateLock.OsLock)); } if (pOtherTask == NULL) return; // EARLY RETURN NdisAcquireSpinLock(&(pOtherTask->Hdr.RmPrivateLock.OsLock)); NdisAcquireSpinLock(&(pTask->Hdr.RmPrivateLock.OsLock)); do { // Now that we have both task's locks, check again if pTask is pending // on pOtherTask // if (pTask->pTaskIAmPendingOn != pOtherTask) { // Oops -- the situation is different than when we started -- quietly // get out of here... // break; } pTask->pTaskIAmPendingOn = NULL; #if RM_EXTRA_CHECKING RmUnlinkObjectsEx( &pTask->Hdr, &pOtherTask->Hdr, 0x6992b7a1, RM_PRIVATE_ASSOC_LINK_TASKPENDINGON, RM_PRIVATE_ASSOC_LINK_TASKBLOCKS, pSR ); #else // !RM_EXTRA_CHECKING RmUnlinkObjects(&pTask->Hdr, &pOtherTask->Hdr, pSR); #endif // !RM_EXTRA_CHECKING RemoveEntryList(&pTask->linkFellowPendingTasks); pTask->linkFellowPendingTasks.Flink = NULL; pTask->linkFellowPendingTasks.Blink = NULL; if (CHECK_RM_TASK_STATE(pTask, RMTSKSTATE_PENDING)) { fResumeTask = TRUE; } else { // // We shouldn't get here -- after we are pending on another task... // ASSERTEX(!"Invalid state", pTask); break; } } while (FALSE); NdisReleaseSpinLock(&(pTask->Hdr.RmPrivateLock.OsLock)); NdisReleaseSpinLock(&(pOtherTask->Hdr.RmPrivateLock.OsLock)); RmTmpDereferenceObject(&(pOtherTask->Hdr), pSR); if (fResumeTask) { RmResumeTask( pTask, UserParam, // SuspendCompletionParam pSR ); } RM_ASSERT_NOLOCKS(pSR); EXIT() } VOID RmInitializeHashTable( PRM_HASH_INFO pHashInfo, PVOID pAllocationContext, PRM_HASH_TABLE pHashTable ) /*++ Routine Description: Initialize a hash table data structure. Caller is responsible for serializing access to the hash table structure. Arguments: pHashInfo - Points to static information about the hash table pAllocationContext - Passed to the allocation and deallocation functions (pHashInfo->pfnTableAllocator and pHashInfo0->pfnTableDeallocator) which are used to dynamically grow /shrink the hash table. pHashTable - Points to uninitialized memory that is to contain the hash table. --*/ { NdisZeroMemory(pHashTable, sizeof(*pHashTable)); pHashTable->pHashInfo = pHashInfo; pHashTable->pAllocationContext = pAllocationContext; pHashTable->pTable = pHashTable->InitialTable; pHashTable->TableLength = sizeof(pHashTable->InitialTable) /sizeof(pHashTable->InitialTable[0]); } VOID RmDeinitializeHashTable( PRM_HASH_TABLE pHashTable ) /*++ Routine Description: Deinitialize a previously-initialized a hash table data structure. There must be no items in the hash table when this function is called. Caller is responsible for serializing access to the hash table structure. Arguments: pHashTable - Hash table to be deinitialized. --*/ { PRM_HASH_LINK *pTable = pHashTable->pTable; ASSERTEX(pHashTable->NumItems == 0, pHashTable); if (pTable != pHashTable->InitialTable) { NdisZeroMemory(pTable, pHashTable->TableLength*sizeof(*pTable)); pHashTable->pHashInfo->pfnTableDeallocator( pTable, pHashTable->pAllocationContext ); } NdisZeroMemory(pHashTable, sizeof(*pHashTable)); } BOOLEAN RmLookupHashTable( PRM_HASH_TABLE pHashTable, PRM_HASH_LINK ** pppLink, PVOID pvRealKey ) /*++ Routine Description: Lookup an item in the hash table and/or find the place where the item is to be inserted. Caller is expected to serialize access to the hash table. OK to use read-locks to serialize access. Return value: TRUE if item found; false otherwise. On return, *pppLink is set to a the location containing a pointer to a RM_HASH_LINK. If the return value is TRUE, the latter pointer points to the found RM_HASH_LINK. If the return value is FALSE, the location is where the item is to be inserted, if required. Arguments: pHashTable - Hash table to look up pppLink - place to store a pointer to a link which points to an item (see above for details). pvRealKey - Key used to lookup item. Return Value: TRUE if item is found FALSE otherwise. --*/ { PRM_HASH_LINK *ppLink, pLink; UINT LinksTraversed = 0; UINT TableLength = pHashTable->TableLength; PFN_RM_COMPARISON_FUNCTION pfnCompare = pHashTable->pHashInfo->pfnCompare; BOOLEAN fRet = FALSE; ULONG uHash = pHashTable->pHashInfo->pfnHash(pvRealKey); for ( ppLink = pHashTable->pTable + (uHash%TableLength); (pLink = *ppLink) != NULL; ppLink = &(pLink->pNext), LinksTraversed++) { if (pLink->uHash == uHash && pfnCompare(pvRealKey, pLink)) { // found it // fRet = TRUE; break; } } // Update stats // rmUpdateHashTableStats(&pHashTable->Stats, LinksTraversed); *pppLink = ppLink; return fRet; } BOOLEAN RmNextHashTableItem( PRM_HASH_TABLE pHashTable, PRM_HASH_LINK pCurrentLink, // OPTIONAL PRM_HASH_LINK * ppNextLink ) /*++ Routine Description: Find the first (if pCurrentLink is NULL) or "next" (if pCurrentLink is not NULL) item in the hash table. Caller is expected to serialize access to the hash table. OK to use read-locks to serialize access. NOTE: The "next" item returned is in no particular order. Arguments: pHashTable - Hash table to look up pCurrentLink - if non-NULL, points to an existing hash link in the hash table. ppLinkLink - place to store a pointer to the link "after" pCurrentLink, or the first link (if pCurrentLink is NULL). Return Value: TRUE if there is a "next" item. FALSE otherwise. --*/ { PRM_HASH_LINK pLink, *ppLink, *ppLinkEnd; UINT TableLength; ppLink = pHashTable->pTable; TableLength = pHashTable->TableLength; ppLinkEnd = ppLink + TableLength; if (pCurrentLink != NULL) { #if DBG { // Make sure this link is valid! pLink = *(ppLink + (pCurrentLink->uHash % TableLength)); while (pLink != NULL && pLink != pCurrentLink) { pLink = pLink->pNext; } if (pLink != pCurrentLink) { ASSERTEX(!"Invalid pCurrentLink", pCurrentLink); *ppNextLink = NULL; return FALSE; // EARLY RETURN } } #endif // DBG if (pCurrentLink->pNext != NULL) { // Found a next link. // *ppNextLink = pCurrentLink->pNext; return TRUE; // EARLY RETURN } else { // End of current bucket, move to next one. // We check later if we've gone past the end of the table. // ppLink += (pCurrentLink->uHash % TableLength) + 1; } } // Look for next non-null item. // for ( ; ppLink < ppLinkEnd; ppLink++) { pLink = *ppLink; if (pLink != NULL) { *ppNextLink = pLink; return TRUE; // EARLY RETURN } } *ppNextLink = NULL; return FALSE; } VOID RmAddHashItem( PRM_HASH_TABLE pHashTable, PRM_HASH_LINK * ppLink, PRM_HASH_LINK pLink, PVOID pvKey ) /*++ Routine Description: Add an item to the hash table at the specified location. Caller is expected to serialize access to the hash table. Arguments: pHashTable - Hash table in which to add item. ppLink - Points to place within table to add new item. pLink - New item to add. pvKey - key associated with the item. TODO: pvKey is only used to compute uHash -- consider passing in uHash directly. --*/ { pLink->uHash = pHashTable->pHashInfo->pfnHash(pvKey); pLink->pNext = *ppLink; *ppLink = pLink; pHashTable->NumItems++; // TODO: if required, resize } VOID RmRemoveHashItem( PRM_HASH_TABLE pHashTable, PRM_HASH_LINK pLinkToRemove ) /*++ Routine Description: Remove an item from the hash table. Caller is expected to serialize access to the hash table. (debug only): Asserts if pLinkToRemove is no in the hash table. Arguments: pHashTable - Hash table in which to add item. pLinkToRemove - Link to remove. --*/ { PRM_HASH_LINK *ppLink, pLink; UINT TableLength = pHashTable->TableLength; ULONG uHash = pLinkToRemove->uHash; BOOLEAN fFound = FALSE; for ( ppLink = pHashTable->pTable + (uHash%TableLength); (pLink = *ppLink) != NULL; ppLink = &(pLink->pNext)) { if (pLink == pLinkToRemove) { // found it -- remove it and get out. // RM_PRIVATE_UNLINK_NEXT_HASH(pHashTable, ppLink); pLink->pNext = NULL; // Important, so that enumeration works. fFound=TRUE; break; } } // TODO: if required, resize ASSERT(fFound); } VOID RmEnumHashTable( PRM_HASH_TABLE pHashTable, PFN_ENUM_HASH_TABLE pfnEnumerator, PVOID pvContext, PRM_STACK_RECORD pSR ) /*++ Routine Description: Calls function pfnEnumerator for each item in the hash table. Caller is expected to serialize access to the hash table. Arguments: pHashTable - Hash table to enumerate. pfnEnumerator - Enumerator function. pvContext - Opaque context passed to enumerator function. --*/ { PRM_HASH_LINK *ppLink, *ppLinkEnd; ppLink = pHashTable->pTable; ppLinkEnd = ppLink + pHashTable->TableLength; for ( ; ppLink < ppLinkEnd; ppLink++) { PRM_HASH_LINK pLink = *ppLink; while (pLink != NULL) { pfnEnumerator( pLink, pvContext, pSR ); pLink = pLink->pNext; } } } VOID RmEnumerateObjectsInGroup( PRM_GROUP pGroup, PFN_RM_GROUP_ENUMERATOR pfnEnumerator, PVOID pvContext, INT fStrong, PRM_STACK_RECORD pSR ) /*++ Routine Description: Calls function pfnEnumerator for each item in the group, until the funcition return FALSE. WARNING: Enumeration is "STRONG" -- the group lock is held during the whole enumeration process. The enumerator function is therefore called at DPR level, and more importantly, the enumerator function avoid locking anything to avoid risk of deadlock. Specifically, the enumerator function MUST NOT lock the object -- if any other thread has called a group-related RM function with the object's lock held, we WILL deadlock. This function should only be used to access parts of the object that do not need to be protected by the objects lock. If locking needs to be performed, use RmWeakEnumerateObjectsInGroup. Arguments: pGroup - Hash table to enumerate. pfnEnumerator - Enumerator function. pvContext - Opaque context passed to enumerator function. fStrong - MUST be TRUE. --*/ { if (fStrong) { RM_STRONG_ENUMERATION_CONTEXT Ctxt; Ctxt.pfnObjEnumerator = pfnEnumerator; Ctxt.pvCallerContext = pvContext; Ctxt.fContinue = TRUE; NdisAcquireSpinLock(&pGroup->OsLock); RmEnumHashTable( &pGroup->HashTable, rmEnumObjectInGroupHashTable, // pfnEnumerator &Ctxt, // context pSR ); NdisReleaseSpinLock(&pGroup->OsLock); } else { ASSERT(!"Unimplemented"); } } VOID RmWeakEnumerateObjectsInGroup( PRM_GROUP pGroup, PFN_RM_GROUP_ENUMERATOR pfnEnumerator, PVOID pvContext, PRM_STACK_RECORD pSR ) /*++ Routine Description: Calls function pfnEnumerator for each item in the group, until the funcition return FALSE. Enumeration is "WEAK" -- the group lock is NOT held the whole time, and is not held when the enumerator function is called. A snapshot of the entire group is first taken with the group lock held, and each object is tempref'd. The group lock is then released and the enumerator function is called for each object in the snapshot. The objects are then derefd. NOTE: It is possible that when the enumeration function is called for an object, the object is no longer in the group. The enumeration function can lock the object and check its internal state to determine if it is still relevant to process the object. Arguments: pGroup - Hash table to enumerate. pfnEnumerator - Enumerator function. pvContext - Opaque context passed to enumerator function. --*/ { #define RM_SMALL_GROUP_SIZE 10 #define RM_MAX_ENUM_GROUP_SIZE 100000 PRM_OBJECT_HEADER *ppSnapshot; PRM_OBJECT_HEADER SmallSnapshot[RM_SMALL_GROUP_SIZE]; UINT NumItems = pGroup->HashTable.NumItems; do { RM_WEAK_ENUMERATION_CONTEXT Ctxt; if (NumItems <= RM_SMALL_GROUP_SIZE) { if (NumItems == 0) break; ppSnapshot = SmallSnapshot; } else if (NumItems > RM_MAX_ENUM_GROUP_SIZE) { // TODO: LOG_RETAIL_ERROR ASSERT(FALSE); break; } else { RM_ALLOC( &(void* )ppSnapshot, NumItems*sizeof(PRM_OBJECT_HEADER), MTAG_RMINTERNAL ); if (ppSnapshot == NULL) { ASSERT(FALSE); break; } } Ctxt.ppCurrent = ppSnapshot; Ctxt.ppEnd = ppSnapshot+NumItems; NdisAcquireSpinLock(&pGroup->OsLock); RmEnumHashTable( &pGroup->HashTable, rmConstructGroupSnapshot, // pfnEnumerator &Ctxt, // context pSR ); NdisReleaseSpinLock(&pGroup->OsLock); ASSERT(Ctxt.ppCurrent >= ppSnapshot); ASSERT(Ctxt.ppCurrent <= Ctxt.ppEnd); // Fix up ppEnd to point to the last actually-filled pointer. // Ctxt.ppEnd = Ctxt.ppCurrent; Ctxt.ppCurrent = ppSnapshot; for (;Ctxt.ppCurrent < Ctxt.ppEnd; Ctxt.ppCurrent++) { pfnEnumerator( *Ctxt.ppCurrent, pvContext, pSR ); RmTmpDereferenceObject(*Ctxt.ppCurrent, pSR); } if (ppSnapshot != SmallSnapshot) { RM_FREE(ppSnapshot); ppSnapshot = NULL; } } while (FALSE); } //========================================================================= // L O C A L F U N C T I O N S //========================================================================= VOID rmDerefObject( PRM_OBJECT_HEADER pObject, PRM_STACK_RECORD pSR ) /*++ Routine Description: Dereference object pObject. Deallocate it if the reference count goes to zero. --*/ { ULONG Refs; ENTER("rmDerefObject", 0x5f9d81dd) ASSERT(RM_OBJECT_IS_ALLOCATED(pObject)); // // On entry, the ref count should be at-least 2 -- one the // explicit ref added in RmAllocateObject, and the 2nd the ref due to // the link to the parent. // // Exception to the above: if the object has no parent, the refcount should be // at-least 1. // // Deref the ref added in RmAllocateObject, and if the ref count is now <=1, // we actually unlink and free the object. // Refs = NdisInterlockedDecrement(&pObject->TotRefs); if (Refs <= 1) { PRM_OBJECT_HEADER pParentObject; RMPRIVATELOCK(pObject, pSR); // // Unlink from parent, if there is one... // pParentObject = pObject->pParentObject; pObject->pParentObject = NULL; #if RM_TRACK_OBJECT_TREE // Verify that there are no siblings... // RETAILASSERTEX(IsListEmpty(&pObject->listChildren), pObject); #endif // RM_TRACK_OBJECT_TREE RMPRIVATEUNLOCK(pObject, pSR); if (pParentObject != NULL) { ASSERTEX(!RMISALLOCATED(pObject), pObject); ASSERTEX(Refs == 1, pObject); #if RM_TRACK_OBJECT_TREE RMPRIVATELOCK(pParentObject, pSR); // Remove object from parent's list of children. // RETAILASSERTEX( !IsListEmpty(&pParentObject->listChildren), pObject); RemoveEntryList(&pObject->linkSiblings); RMPRIVATEUNLOCK(pParentObject, pSR); #endif // RM_TRACK_OBJECT_TREE #if RM_EXTRA_CHECKING RmUnlinkObjectsEx( pObject, pParentObject, 0xac73e169, RM_PRIVATE_ASSOC_LINK_CHILDOF, RM_PRIVATE_ASSOC_LINK_PARENTOF, pSR ); #else // !RM_EXTRA_CHECKING RmUnlinkObjects(pObject, pParentObject, pSR); #endif // !RM_EXTRA_CHECKING } else if (Refs == 0) { // // Free to deallocate this thing... // ASSERTEX(!RMISALLOCATED(pObject), pObject); #if RM_EXTRA_CHECKING rmDbgDeinitializeDiagnosticInfo(pObject, pSR); #endif // RM_EXTRA_CHECKING RM_MARK_OBJECT_AS_DEALLOCATED(pObject); if (pObject->pStaticInfo->pfnDelete!= NULL) { TR_INFO(( "Actually freeing 0x%p (%s)\n", pObject, pObject->szDescription )); pObject->pStaticInfo->pfnDelete(pObject, pSR); } } } EXIT() } VOID rmLock( PRM_LOCK pLock, #if RM_EXTRA_CHECKING UINT uLocID, PFNLOCKVERIFIER pfnVerifier, PVOID pVerifierContext, #endif //RM_EXTRA_CHECKING PRM_STACK_RECORD pSR ) /*++ Routine Description: Lock pLock. Arguments: pLock - Lock to lock. LocID - Arbitrary ID, typically representing the source location from which this function is called. Following are for debug only: pfnVerifier - Optional function that is called just after locking pfnVerifierContext - Passed in call to pfnVerifier --*/ { //UINT Level = pSR->LockInfo.CurrentLevel; RM_LOCKING_INFO li; RETAILASSERTEX(pLock->Level > pSR->LockInfo.CurrentLevel, pLock); RETAILASSERTEX(pSR->LockInfo.pNextFree < pSR->LockInfo.pLast, pLock); pSR->LockInfo.CurrentLevel = pLock->Level; // Save information about this lock in the stack record. // li.pLock = pLock; #if RM_EXTRA_CHECKING li.pfnVerifier = pfnVerifier; li.pVerifierContext = pVerifierContext; #endif //RM_EXTRA_CHECKING *(pSR->LockInfo.pNextFree++) = li; // struct copy. // Get the lock. // TODO: uncomment the following optimization... //if (Level) //{ // NdisDprAcquireSpinLock(&pLock->OsLock); //} //else //{ NdisAcquireSpinLock(&pLock->OsLock); //} #if RM_EXTRA_CHECKING ASSERTEX(pLock->DbgInfo.uLocID == 0, pLock); ASSERTEX(pLock->DbgInfo.pSR == NULL, pLock); pLock->DbgInfo.uLocID = uLocID; pLock->DbgInfo.pSR = pSR; // Call the verifier routine if there is one. // if (pfnVerifier) { pfnVerifier(pLock, TRUE, pVerifierContext, pSR); } #endif //RM_EXTRA_CHECKING } VOID rmUnlock( PRM_LOCK pLock, PRM_STACK_RECORD pSR ) /*++ Routine Description: Unlock pLock. Debug only: if there is a verifier function associated with this lock we call it just before unlocking pLock. Arguments: pLock - Lock to unlock. --*/ { RM_LOCKING_INFO * pLI; pSR->LockInfo.pNextFree--; pLI = pSR->LockInfo.pNextFree; RETAILASSERTEX(pLI->pLock == pLock, pLock); ASSERTEX(pLock->DbgInfo.pSR == pSR, pLock); ASSERTEX(pLock->Level == pSR->LockInfo.CurrentLevel, pLock); pLI->pLock = NULL; if (pLI > pSR->LockInfo.pFirst) { PRM_LOCK pPrevLock = (pLI-1)->pLock; pSR->LockInfo.CurrentLevel = pPrevLock->Level; ASSERTEX(pPrevLock->DbgInfo.pSR == pSR, pPrevLock); } else { pSR->LockInfo.CurrentLevel = 0; } #if RM_EXTRA_CHECKING // Call the verifier routine if there is one. // if (pLI->pfnVerifier) { pLI->pfnVerifier(pLock, FALSE, pLI->pVerifierContext, pSR); pLI->pfnVerifier = NULL; pLI->pVerifierContext = NULL; } pLock->DbgInfo.uLocID = 0; pLock->DbgInfo.pSR = NULL; #endif //RM_EXTRA_CHECKING // Release the lock. // NdisReleaseSpinLock(&pLock->OsLock); } #if RM_EXTRA_CHECKING ULONG rmPrivateLockVerifier( PRM_LOCK pLock, BOOLEAN fLock, PVOID pContext, PRM_STACK_RECORD pSR ) /*++ Routine Description: (Debug only) The Verifier function for an object's RmPrivateLock. Arguments: pLock - Lock being locked/unlocked fLock - TRUE if lock has just been locked. FALSE if lock is about to be unlocked. Return Value: Unused: TODO make return value VOID. --*/ { ENTER("rmPrivateLockVerifier", 0xc3b63ac5) TR_VERB(("Called with pLock=0x%p, fLock=%lu, pContext=%p\n", pLock, fLock, pContext, pSR)); EXIT() return 0; } ULONG rmVerifyObjectState( PRM_LOCK pLock, BOOLEAN fLock, PVOID pContext, PRM_STACK_RECORD pSR ) /*++ Routine Description: (Debug only) Uses the object's verification function (if there is one) to compute a signature that is checked each time the object is locked, and is updated each time the object is unlocked. Assert if this signature has changed while the object was supposedly unlocked. Also: Update RM_OBJECT_HEADER.pDiagInfo->PrevState if there is been a change of state while the object was locked. Arguments: pLock - Lock being locked/unlocked fLock - TRUE if lock has just been locked. FALSE if lock is about to be unlocked. pContext - Actually pointer to object being locked. Return Value: Unused: TODO make return value VOID. --*/ { PRM_OBJECT_HEADER pObj = (PRM_OBJECT_HEADER) pContext; PRM_OBJECT_DIAGNOSTIC_INFO pDiagInfo = pObj->pDiagInfo; ULONG NewChecksum; ENTER("rmVerifyObjectState", 0xb8ff7a67) TR_VERB(("Called with pLock=0x%p, fLock=%lu, pObj=%p\n", pLock, fLock, pObj, pSR)); if (pDiagInfo != NULL && !(pDiagInfo->DiagState & fRM_PRIVATE_DISABLE_LOCK_CHECKING)) { // Compute the new checksum and as part of that call the // object-specific verifier if there is one.... // { PFNLOCKVERIFIER pfnVerifier; // We verify that the objset-specific state was not modified // without the lock held. This is done by including the object-specific // state in the checksum computation. // NewChecksum = pObj->State; // Then, if the object has a verifier function, we call it, and // fold in the return value into the checkum. // pfnVerifier = pObj->pStaticInfo->pfnLockVerifier; if (pfnVerifier != NULL) { NewChecksum ^= pfnVerifier(pLock, fLock, pObj, pSR); } } if (fLock) // We've just locked the object. { // First thing we do is to save the current value of pObj->State in // the TmpState location -- we'll look at it again on unlocking. // pDiagInfo->TmpState = pObj->State; // Now we compare the new checksum value with the value that wase // saved the last time this object was locked... // Special case: old Checksum was 0 -- as it is on initialization. // if (NewChecksum != pDiagInfo->Checksum && pDiagInfo->Checksum) { TR_WARN(( "Object 0x%p (%s) possibly modified without lock held!\n", pObj, pObj->szDescription )); // Unfortunately we hit this assert because there are places where // the same lock is shared by many objects and #if 0 // Give users the option to ignore further validation on this // object. // TR_FATAL(( "To skip this assert, type \"ed 0x%p %lx; g\"\n", &pDiagInfo->DiagState, pDiagInfo->DiagState | fRM_PRIVATE_DISABLE_LOCK_CHECKING )); ASSERTEX(!"Object was modified without lock held!", pObj); #endif // 0 } } else // We're just about to unlock the object.... { // Update the signature... // pDiagInfo->Checksum = NewChecksum; // If there has been a change in state between locking and unlockng // this object, save the previous state. // if (pDiagInfo->TmpState != pObj->State) { pDiagInfo->PrevState = pDiagInfo->TmpState; } } } EXIT() return 0; } #endif // RM_EXTRA_CHECKING VOID rmEndTask( PRM_TASK pTask, NDIS_STATUS Status, PRM_STACK_RECORD pSR ) /*++ Routine Description: Send the RM_TASKOP_END to the task handler, and resume any tasks pending on pTask. Arguments: pTask - Task to end. Status - Completion status -- passed on to the task handler. --*/ { ENTER("rmEndtask", 0x5060d952) PRM_TASK pPendingTask; RM_ASSERT_NOLOCKS(pSR); TR_INFO(( "ENDING Task 0x%p (%s); Status = 0x%lx\n", pTask, pTask->Hdr.szDescription, Status )); // TODO: could change behavior so that we use the return value, but // currently we ignore it... // pTask->pfnHandler( pTask, RM_TASKOP_END, Status, // UserParam is overloaded here. pSR ); RM_ASSERT_NOLOCKS(pSR); do { pPendingTask = NULL; RMPRIVATELOCK(&pTask->Hdr, pSR); if (!IsListEmpty(&pTask->listTasksPendingOnMe)) { pPendingTask = CONTAINING_RECORD( (pTask->listTasksPendingOnMe.Flink), RM_TASK, linkFellowPendingTasks ); RmTmpReferenceObject(&pPendingTask->Hdr, pSR); } RMPRIVATEUNLOCK(&pTask->Hdr, pSR); if (pPendingTask != NULL) { RmCancelPendOnOtherTask( pPendingTask, pTask, Status, pSR ); RmTmpDereferenceObject(&pPendingTask->Hdr, pSR); } } while(pPendingTask != NULL); RM_ASSERT_NOLOCKS(pSR); EXIT() } NDIS_STATUS rmAllocatePrivateTask( IN PRM_OBJECT_HEADER pParentObject, IN PFN_RM_TASK_HANDLER pfnHandler, IN UINT Timeout, IN const char * szDescription, OPTIONAL OUT PRM_TASK *ppTask, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: Allocate and initialize a task of subtype RM_PRIVATE_TASK. Arguments: pParentObject - Object that is to be the parent of the allocated task. pfnHandler - The task handler for the task. Timeout - Unused. szDescription - Text describing this task. ppTask - Place to store pointer to the new task. Return Value: NDIS_STATUS_SUCCESS if we could allocate and initialize the task. NDIS_STATUS_RESOURCES otherwise --*/ { RM_PRIVATE_TASK *pRmTask; NDIS_STATUS Status; RM_ALLOCSTRUCT(pRmTask, MTAG_TASK); // TODO use lookaside lists. *ppTask = NULL; if (pRmTask != NULL) { RM_ZEROSTRUCT(pRmTask); RmInitializeTask( &(pRmTask->TskHdr), pParentObject, pfnHandler, &RmPrivateTasks_StaticInfo, szDescription, Timeout, pSR ); *ppTask = &(pRmTask->TskHdr); Status = NDIS_STATUS_SUCCESS; } else { Status = NDIS_STATUS_RESOURCES; } return Status; } NDIS_STATUS rmTaskUnloadGroup( IN struct _RM_TASK * pTask, IN RM_TASK_OPERATION Code, IN UINT_PTR UserParam, IN PRM_STACK_RECORD pSR ) /*++ Routine Description: This task is responsible for unloading all the objects in the group. pTask is a pointer to TASK_UNLOADGROUP, and that structure is expected to be initialized, including containing the pGroup to unload. Arguments: UserParam for (Code == RM_TASKOP_START) : unused --*/ { NDIS_STATUS Status = NDIS_STATUS_FAILURE; TASK_UNLOADGROUP *pMyTask = (TASK_UNLOADGROUP*) pTask; PRM_GROUP pGroup = pMyTask->pGroup; BOOLEAN fContinueUnload = FALSE; ENTER("TaskUnloadGroup", 0x964ee422) enum { PEND_WaitOnOtherTask, PEND_UnloadObject }; switch(Code) { case RM_TASKOP_START: { // If there is already an unload task bound to pGroup, we // pend on it. // NdisAcquireSpinLock(&pGroup->OsLock); if (pGroup->pUnloadTask != NULL) { PRM_TASK pOtherTask = pGroup->pUnloadTask; TR_WARN(("unload task 0x%p already bound to pGroup 0x%p; pending on it.\n", pOtherTask, pGroup)); RmTmpReferenceObject(&pOtherTask->Hdr, pSR); NdisReleaseSpinLock(&pGroup->OsLock); RmPendTaskOnOtherTask( pTask, PEND_WaitOnOtherTask, pOtherTask, pSR ); RmTmpDereferenceObject(&pOtherTask->Hdr, pSR); Status = NDIS_STATUS_PENDING; break; } else if (!pGroup->fEnabled) { // // Presumably this group has already been unloaded of all objects // and is simply sitting around. We complete right away. // Status = NDIS_STATUS_SUCCESS; } else { // // We're the 1st ones here -- continue on to unloading objects, if // any... // pGroup->pUnloadTask = pTask; pGroup->fEnabled = FALSE; // This will prevent new objects from // being added and from the hash table // itself from changing size. pMyTask->uIndex = 0; // This keeps track of where we are in the // hash table. fContinueUnload = TRUE; } NdisReleaseSpinLock(&pGroup->OsLock); } break; case RM_TASKOP_PENDCOMPLETE: { switch(RM_PEND_CODE(pTask)) { case PEND_WaitOnOtherTask: { // // Nothing to do -- finish task. // Status = NDIS_STATUS_SUCCESS; } break; case PEND_UnloadObject: { // // Just done unloading an object; unload another if required. // fContinueUnload = TRUE; Status = NDIS_STATUS_SUCCESS; } break; default: ASSERTEX(!"Unknown pend code!", pTask); break; } } break; case RM_TASKOP_END: { BOOLEAN fSignal; NdisAcquireSpinLock(&pGroup->OsLock); // Clear ourselves from pGroup, if we're there. // if (pGroup->pUnloadTask == pTask) { pGroup->pUnloadTask = NULL; } fSignal = pMyTask->fUseEvent; pMyTask->fUseEvent = FALSE; NdisReleaseSpinLock(&pGroup->OsLock); if (fSignal) { NdisSetEvent(&pMyTask->BlockEvent); } Status = NDIS_STATUS_SUCCESS; } break; default: ASSERTEX(!"Unknown task op", pTask); break; } // switch (Code) if (fContinueUnload) { do { PRM_HASH_LINK *ppLink, *ppLinkEnd; UINT uIndex; PRM_HASH_LINK pLink; PRM_OBJECT_HEADER pObj; PRM_TASK pUnloadObjectTask; NdisAcquireSpinLock(&pGroup->OsLock); uIndex = pMyTask->uIndex; // // With fEnabled set to FALSE by us, we expect the following: // (a) pHashTable->pTable is going to stay the same size. // (b) No items are going to be added or removed by anyone else. // // Find the next non-empty hash table entry, starting at // offset pMyTask->uIndex. // ASSERTEX(!pGroup->fEnabled, pGroup); ASSERTEX(uIndex <= pGroup->HashTable.TableLength, pGroup); ppLinkEnd = ppLink = pGroup->HashTable.pTable; ppLink += uIndex; ppLinkEnd += pGroup->HashTable.TableLength; while (ppLink < ppLinkEnd && *ppLink == NULL) { ppLink++; } // Update index to our current position in the hash table. // pMyTask->uIndex = (UINT)(ppLink - pGroup->HashTable.pTable); if (ppLink >= ppLinkEnd) { // // We're done... // NdisReleaseSpinLock(&pGroup->OsLock); Status = NDIS_STATUS_SUCCESS; break; } // // Found another object to unload... // We'll allocate a task (pUnloadObjectTask) to unload that object, // pend ourselves on it, and then start it. // // pLink = *ppLink; pObj = CONTAINING_RECORD(pLink, RM_OBJECT_HEADER, HashLink); RmTmpReferenceObject(pObj, pSR); ASSERT(pObj->pStaticInfo == pGroup->pStaticInfo); NdisReleaseSpinLock(&pGroup->OsLock); Status = pMyTask->pfnUnloadTaskAllocator( pObj, // pParentObject, pMyTask->pfnTaskUnloadObjectHandler, // pfnHandler, 0, // Timeout, "Task:Unload Object", &pUnloadObjectTask, pSR ); if (FAIL(Status)) { // Aargh... we couldn't allocate a task to unload this object. // We'll return quietly, leaving the other objects intact... // ASSERTEX(!"Couldn't allocat unload task for object.", pObj); RmTmpDereferenceObject(pObj, pSR); break; } RmTmpDereferenceObject(pObj, pSR); #if OBSOLETE // See 03/26/1999 notes.txt entry "Some proposed ..." RmPendTaskOnOtherTask( pTask, PEND_UnloadObject, pUnloadObjectTask, // task to pend on pSR ); (void)RmStartTask( pUnloadObjectTask, 0, // UserParam (unused) pSR ); Status = NDIS_STATUS_PENDING; #else // !OBSOLETE RmTmpReferenceObject(&pUnloadObjectTask->Hdr, pSR); RmStartTask( pUnloadObjectTask, 0, // UserParam (unused) pSR ); Status = RmPendOnOtherTaskV2( pTask, PEND_UnloadObject, pUnloadObjectTask, pSR ); RmTmpDereferenceObject(&pUnloadObjectTask->Hdr, pSR); if (PEND(Status)) { break; } #endif // !OBSOLETE } #if OBSOLETE // See 03/26/1999 notes.txt entry "Some proposed ..." while (FALSE); #else // !OBSOLETE while (TRUE); #endif // !OBSOLETE } // if(fContinueUnload) RM_ASSERT_NOLOCKS(pSR); EXIT() return Status; } #if RM_EXTRA_CHECKING BOOLEAN rmDbgAssociationCompareKey( PVOID pKey, PRM_HASH_LINK pItem ) /*++ Routine Description: Comparison function used to test for exact equality of items in the debug association table. Arguments: pKey - Actually a pointer to an RM_PRIVATE_DBG_ASSOCIATION structure. pItem - Points to RM_PRIVATE_DBG_ASSOCIATION.HashLink. Return Value: TRUE IFF the (Entity1, Entity2 and AssociationID) fields of the key exactly match the corresponding fields of CONTAINING_RECORD(pItem, RM_PRIVATE_DBG_ASSOCIATION, HashLink); --*/ { RM_PRIVATE_DBG_ASSOCIATION *pA = CONTAINING_RECORD(pItem, RM_PRIVATE_DBG_ASSOCIATION, HashLink); // pKey is actually a RM_PRIVATE_DBG_ASSOCIATION structure. // RM_PRIVATE_DBG_ASSOCIATION *pTrueKey = (RM_PRIVATE_DBG_ASSOCIATION *) pKey; if ( pA->Entity1 == pTrueKey->Entity1 && pA->Entity2 == pTrueKey->Entity2 && pA->AssociationID == pTrueKey->AssociationID) { return TRUE; } else { return FALSE; } } ULONG rmDbgAssociationHash( PVOID pKey ) /*++ Routine Description: Hash generating function used to compute a ULONG-sized hash from key, which is actually a pointer to an RM_PRIVATE_DBG_ASSOCIATION structure. Arguments: pKey - Actually a pointer to an RM_PRIVATE_DBG_ASSOCIATION structure. Return Value: ULONG-sized hash generated from the (Entity1, Entity2 and AssociationID) fields of the key. --*/ { // pKey is actually a RM_PRIVATE_DBG_ASSOCIATION structure. // RM_PRIVATE_DBG_ASSOCIATION *pTrueKey = (RM_PRIVATE_DBG_ASSOCIATION *) pKey; ULONG_PTR big_hash; big_hash = pTrueKey->Entity1; big_hash ^= pTrueKey->Entity2; big_hash ^= pTrueKey->AssociationID; // Warning: Below, the return value would be truncated in 64-bit. // That tolerable because after all this is just a hash. // TODO: for 64-bit, consider xoring hi- and lo- DWORD instead of truncationg. // return (ULONG) big_hash; } // Static hash information use for the hash table (in the diagnostic information // of each object) that keeps track of associations. // RM_HASH_INFO rmDbgAssociation_HashInfo = { NULL, // pfnTableAllocator NULL, // pfnTableDeallocator rmDbgAssociationCompareKey, // fnCompare // Function to generate a ULONG-sized hash. // rmDbgAssociationHash // pfnHash }; VOID rmDbgInitializeDiagnosticInfo( PRM_OBJECT_HEADER pObject, PRM_STACK_RECORD pSR ) /*++ Routine Description: Allocate and initialize the diagnostic information associated with object pObject. This includes initializing the hash table used to keep track of arbitrary associations. --*/ { ENTER("InitializeDiagnosticInfo", 0x55db57a2) PRM_OBJECT_DIAGNOSTIC_INFO pDiagInfo; TR_VERB((" pObj=0x%p\n", pObject)); // TODO: use lookaside lists for the allocation of these objects. // RM_ALLOCSTRUCT(pDiagInfo, MTAG_DBGINFO); if (pDiagInfo != NULL) { RM_ZEROSTRUCT(pDiagInfo); NdisAllocateSpinLock(&pDiagInfo->OsLock); RmInitializeHashTable( &rmDbgAssociation_HashInfo, NULL, // pAllocationContext &pDiagInfo->AssociationTable ); pObject->pDiagInfo = pDiagInfo; pDiagInfo->pOwningObject = pObject; // Initialize the per-object log list. // InitializeListHead(&pDiagInfo->listObjectLog); } } VOID rmDbgFreeObjectLogEntries( LIST_ENTRY *pObjectLog ) /*++ Routine Description: Remove and free all items from the object log pObjectLog. It is assumed that no one is trying to add items to this log at this time. --*/ { LIST_ENTRY *pLink=NULL, *pNextLink=NULL; if (IsListEmpty(pObjectLog)) return; // EARLY RETURN NdisAcquireSpinLock(&RmGlobals.GlobalOsLock); for( pLink = pObjectLog->Flink; pLink != pObjectLog; pLink = pNextLink) { RM_DBG_LOG_ENTRY *pLogEntry; LIST_ENTRY *pLinkGlobalLog; pLogEntry = CONTAINING_RECORD(pLink, RM_DBG_LOG_ENTRY, linkObjectLog); pLinkGlobalLog = &pLogEntry->linkGlobalLog; // Remove entry from global log. // We don't bother removing the entry from the local log list, because // it's going away anyway. // RemoveEntryList(pLinkGlobalLog); // Move to next entry in object's log (which may not be the next entry // in the global log). // pNextLink = pLink->Flink; // Free the buffer in the log entry, if any. // TODO: need to use log buffer deallocation function -- // See notes.txt 03/07/1999 entry "Registering root objects with RM". // For now we assume the this memory was allocated using // NdisAllocateMemory[WithTag]. // if (pLogEntry->pvBuf != NULL) { NdisFreeMemory(pLogEntry->pvBuf, 0, 0); } // Free the log entry itself. // rmDbgDeallocateLogEntry(pLogEntry); } NdisReleaseSpinLock(&RmGlobals.GlobalOsLock); } VOID rmDbgDeinitializeDiagnosticInfo( PRM_OBJECT_HEADER pObject, PRM_STACK_RECORD pSR ) /*++ Routine Description: (Debug only) Deinitialize and free the diagnostic information associated with object pObject. This includes verifying that there are no remaining associations and links. --*/ { ENTER("DeinitializeDiagnosticInfo", 0xa969291f) PRM_OBJECT_DIAGNOSTIC_INFO pDiagInfo = pObject->pDiagInfo; TR_VERB((" pObj=0x%p\n", pObject)); if (pDiagInfo != NULL) { // Free all the per-object log entries. // Note: no one should be trying to add items to this log at this time // because we're aboute to deallocate this object. // { rmDbgFreeObjectLogEntries(&pDiagInfo->listObjectLog); RM_ZEROSTRUCT(&pDiagInfo->listObjectLog); } if (pDiagInfo->AssociationTable.NumItems != 0) { // // Ouch! Associations still left. We print out the associations and then // DebugBreak. // TR_FATAL(( "FATAL: Object 0x%p still has some associations left!\n", pObject )); RmDbgPrintAssociations(pObject, pSR); ASSERT(!"Object has associations left at deallocation time."); } pObject->pDiagInfo = NULL; RmDeinitializeHashTable( &pDiagInfo->AssociationTable ); // // Add any other checks here... // NdisFreeSpinLock(&pDiagInfo->OsLock); RM_FREE(pDiagInfo); } } VOID rmDbgPrintOneAssociation ( PRM_HASH_LINK pLink, PVOID pvContext, PRM_STACK_RECORD pSR ) /*++ Routine Description: Dump a single association. Arguments: pLink - Points to RM_PRIVATE_DBG_ASSOCIATION.HashLink. pvContext - Unused --*/ { RM_PRIVATE_DBG_ASSOCIATION *pA = CONTAINING_RECORD(pLink, RM_PRIVATE_DBG_ASSOCIATION, HashLink); DbgPrint( (char*) (pA->szFormatString), pA->Entity1, pA->Entity2, pA->AssociationID ); } VOID _cdecl rmDefaultDumpEntry ( char *szFormatString, UINT_PTR Param1, UINT_PTR Param2, UINT_PTR Param3, UINT_PTR Param4 ) /*++ Routine Description: Default function to dump the contents of an association. --*/ { DbgPrint( szFormatString, Param1, Param2, Param3, Param4 ); } UINT rmSafeAppend( char *szBuf, const char *szAppend, UINT cbBuf ) /*++ Routine Description: Append szAppend to szBuf, but don't exceed cbBuf, and make sure the resulting string is null-terminated. Return Value: Total length of string (excluding null termination) after append. --*/ { UINT uRet; char *pc = szBuf; char *pcEnd = szBuf+cbBuf-1; // possible overflow, but we check below. const char *pcFrom = szAppend; if (cbBuf==0) return 0; // EARLY RETURN; // Skip to end of szBuf // while (pc < pcEnd && *pc!=0) { pc++; } // Append szAppend while (pc < pcEnd && *pcFrom!=0) { *pc++ = *pcFrom++; } // Append final zero // *pc=0; return (UINT) (UINT_PTR) (pc-szBuf); } #endif //RM_EXTRA_CHECKING VOID rmWorkItemHandler_ResumeTaskAsync( IN PNDIS_WORK_ITEM pWorkItem, IN PVOID pTaskToResume ) /*++ Routine Description: NDIS work item handler which resumes the give task. Arguments: pWorkItem - Work item associated with the handler. pTaskToResume - Actually a pointer to the task to resume. --*/ { PRM_TASK pTask = pTaskToResume; UINT_PTR CompletionParam = pTask->AsyncCompletionParam; RM_DECLARE_STACK_RECORD(sr) ASSERTEX(RMISALLOCATED(&pTask->Hdr), pTask); #if RM_EXTRA_CHECKING // Undo the association added in RmResumeTasyAsync... // RmDbgDeleteAssociation( 0xfc39a878, // Location ID &pTask->Hdr, // pObject CompletionParam, // Instance1 (UINT_PTR) pWorkItem, // Instance2 RM_PRIVATE_ASSOC_RESUME_TASK_ASYNC, // AssociationID &sr ); #endif // RM_EXTRA_CHECKING // Actually resume the task. // RmResumeTask(pTask, CompletionParam, &sr); RM_ASSERT_CLEAR(&sr) } VOID rmTimerHandler_ResumeTaskDelayed( IN PVOID SystemSpecific1, IN PVOID FunctionContext, IN PVOID SystemSpecific2, IN PVOID SystemSpecific3 ) /*++ Routine Description: NDIS timer handler which resumes the give task. WARNING: This handler is also called internally by the RM APIs. Implementation notes: -- see notes.txt 07/14/1999 entry. Arguments: SystemSpecific1 - Unused FunctionContext - Actually a pointer to the task to be resumed. SystemSpecific2 - Unused SystemSpecific3 - Unused --*/ { PRM_TASK pTask = FunctionContext; UINT_PTR CompletionParam = pTask->AsyncCompletionParam; RM_DECLARE_STACK_RECORD(sr) ASSERTEX(RMISALLOCATED(&pTask->Hdr), pTask); #if RM_EXTRA_CHECKING // Undo the association added in RmResumeTasyDelayed... // RmDbgDeleteAssociation( 0xfc39a878, // Location ID &pTask->Hdr, // pObject CompletionParam, // Instance1 (UINT_PTR) NULL, // Instance2 RM_PRIVATE_ASSOC_RESUME_TASK_DELAYED, // AssociationID &sr ); #endif // RM_EXTRA_CHECKING RMPRIVATELOCK(&pTask->Hdr, &sr); ASSERT(RM_CHECK_STATE(pTask, RMTSKDELSTATE_MASK, RMTSKDELSTATE_DELAYED)); RM_SET_STATE(pTask, RMTSKDELSTATE_MASK, 0); RM_SET_STATE(pTask, RMTSKABORTSTATE_MASK, 0); RMPRIVATEUNLOCK(&pTask->Hdr, &sr); // Actually resume the task. // RmResumeTask(pTask, CompletionParam, &sr); RM_ASSERT_CLEAR(&sr) } VOID rmPrivateTaskDelete ( PRM_OBJECT_HEADER pObj, PRM_STACK_RECORD psr ) /*++ Routine Description: Free a private task (which was allocated using rmAllocatePrivateTask. Arguments: pObj - Actually a pointer to a task of subtype RM_PRIVATE_TASK. --*/ { RM_FREE(pObj); } #if RM_EXTRA_CHECKING RM_DBG_LOG_ENTRY * rmDbgAllocateLogEntry(VOID) /*++ Routine Description: Allocate an object log entry. TODO use lookaside lists, and implement per-component global logs. See notes.txt 03/07/1999 entry "Registering root objects with RM". --*/ { RM_DBG_LOG_ENTRY *pLE; RM_ALLOCSTRUCT(pLE, MTAG_DBGINFO); return pLE; } VOID rmDbgDeallocateLogEntry( RM_DBG_LOG_ENTRY *pLogEntry ) /*++ Routine Description: Free an object log entry. TODO use lookaside lists, and implement per-component global logs. See notes.txt 03/07/1999 entry "Registering root objects with RM". --*/ { RM_FREE(pLogEntry); } #endif // RM_EXTRA_CHECKING VOID rmUpdateHashTableStats( PULONG pStats, ULONG LinksTraversed ) /*++ Routine Description: Update the stats (loword == links traversed, hiword == num accesses) --*/ { ULONG OldStats; ULONG Stats; // Clip LinksTraversed to 2^13, or 8192 // if (LinksTraversed > (1<<13)) { LinksTraversed = 1<<13; } Stats = OldStats = *pStats; // If either the loword or hiword of Stats is greater-than 2^13, we // intiger-devide both by 2. We're really only interested in the ratio // of the two, which is preserved by the division. // #define rmSTATS_MASK (0x11<<30|0x11<<14) if (OldStats & rmSTATS_MASK) { Stats >>= 1; Stats &= ~rmSTATS_MASK; } // Compute the updated value of stats.. // "1<<16" below means "one access" // Stats += LinksTraversed | (1<<16); // Update the stats, but only if they haven't already been updated by // someone else. Note that if they HAVE been updated, we will lose this // update. Not a big deal as we are not looking for 100% exact statistics here. // InterlockedCompareExchange(pStats, Stats, OldStats); } VOID rmEnumObjectInGroupHashTable ( PRM_HASH_LINK pLink, PVOID pvContext, PRM_STACK_RECORD pSR ) /*++ Hash table enumerator to implement "STRONG" enumeration -- see RmEnumerateObjectsInGroup. --*/ { PRM_STRONG_ENUMERATION_CONTEXT pCtxt = (PRM_STRONG_ENUMERATION_CONTEXT)pvContext; if (pCtxt->fContinue) { PRM_OBJECT_HEADER pHdr; pHdr = CONTAINING_RECORD(pLink, RM_OBJECT_HEADER, HashLink); pCtxt->fContinue = pCtxt->pfnObjEnumerator( pHdr, pCtxt->pvCallerContext, pSR ); } } VOID rmConstructGroupSnapshot ( PRM_HASH_LINK pLink, PVOID pvContext, PRM_STACK_RECORD pSR ) /*++ Hash table enumerator to construct a snapshot of a group for weak enumeration. See RmWeakEnumerateObjectsInGroup. --*/ { PRM_WEAK_ENUMERATION_CONTEXT pCtxt = (PRM_WEAK_ENUMERATION_CONTEXT)pvContext; if (pCtxt->ppCurrent < pCtxt->ppEnd) { PRM_OBJECT_HEADER pHdr; pHdr = CONTAINING_RECORD(pLink, RM_OBJECT_HEADER, HashLink); RmTmpReferenceObject(pHdr, pSR); *pCtxt->ppCurrent = pHdr; pCtxt->ppCurrent++; } }