/*++ Copyright(c) 1999-2000 Microsoft Corporation Module Name: brdgbuf.c Abstract: Ethernet MAC level bridge. Buffer management section Author: Mark Aiken (original bridge by Jameel Hyder) Environment: Kernel mode driver Revision History: Feb 2000 - Original version --*/ #define NDIS_MINIPORT_DRIVER #define NDIS50_MINIPORT 1 #define NDIS_WDM 1 #pragma warning( push, 3 ) #include #include #pragma warning( pop ) #include "bridge.h" #include "brdgbuf.h" #include "brdgprot.h" #include "brdgmini.h" // =========================================================================== // // PRIVATE DECLARATIONS // // =========================================================================== // // A guess at how many buffer descriptors an average packet indicated on the // no-copy path is likely to have. // // The size of the pool of MDLs used to construct wrapper packets is based on this // guess // #define GUESS_BUFFERS_PER_PACKET 3 // // Transit unicast packets on the no-copy path require one packet descriptor to wrap // them for relay. // // Transit broadcast packets require n descriptors (where n == # of adapters) to // wrap them for relay. // // We can't allow our descriptor usage to reach n^2, which is the worst case to handle // broadcast traffic from all adapters. This number is a guess at how many wrapping // descriptors we will need, on **average**, per packet. The idea is to not run out // of packet descriptors under regular traffic conditions. If running on a machine // with lots of adapters and lots of broadcast traffic, the # of wrapper descriptors // may become a limiting factor if this guess is wrong. // // The size of the wrapper packet descriptor pool is based on this guess. // #define GUESS_AVERAGE_FANOUT 2 // // In case we can't read it out of the registry, use this default value for the // size of the copy packet pool safety buffer. // #define DEFAULT_SAFETY_MARGIN 10 // A percentage (10%) // // In case we can't read it out of the registry, use this default value for the // total memory footprint we are allowed. // #define DEFAULT_MAX_BUF_MEMORY 2 * 1024 * 1024 // 2MB in bytes // // Registry values that hold our config values // const PWCHAR gMaxBufMemoryParameterName = L"MaxBufferMemory"; const PWCHAR gSafetyMarginParameterName = L"SafetyMargin"; // // Constant for different types of quota-restricted packets // typedef enum { BrdgQuotaCopyPacket = 0, BrdgQuotaWrapperPacket = 1 } QUOTA_PACKET_TYPE; // =========================================================================== // // GLOBALS // // =========================================================================== // List of free packet descriptors for copy-receives BSINGLE_LIST_HEAD gFreeCopyPacketList; NDIS_SPIN_LOCK gFreeCopyPacketListLock; // List of free packet descriptors for wrapper packets BSINGLE_LIST_HEAD gFreeWrapperPacketList; NDIS_SPIN_LOCK gFreeWrapperPacketListLock; // Look-aside list for copy-receive buffers NPAGED_LOOKASIDE_LIST gCopyBufferList; BOOLEAN gInitedCopyBufferList = FALSE; // Look-aside list for packet info blocks NPAGED_LOOKASIDE_LIST gPktInfoList; BOOLEAN gInitedPktInfoList = FALSE; // Packet descriptor pools for copy receives and wrapper packets NDIS_HANDLE gCopyPacketPoolHandle = NULL; NDIS_HANDLE gWrapperPacketPoolHandle = NULL; // MDL pools for copy receives and wrapper packets NDIS_HANDLE gCopyBufferPoolHandle = NULL; NDIS_HANDLE gWrapperBufferPoolHandle = NULL; // Spin lock to protect quota information NDIS_SPIN_LOCK gQuotaLock; // Quota information for the local miniport ADAPTER_QUOTA gMiniportQuota; // // Maximum number of available packets of each type // // [0] == Copy packets // [1] == Wrapper packets // ULONG gMaxPackets[2] = { 0L, 0L }; // // Number of packets currently allocated from each pool // // [0] == Copy packets // [1] == Wrapper packets // ULONG gUsedPackets[2] = { 0L, 0L }; #if DBG ULONG gMaxUsedPackets[2] = { 0L, 0L }; #endif // // Amount of packets to keep as a buffer in each pool (the maximum consumption // of any single adapter is gMaxPackets[X] - gSafetyBuffer[X]. // // These values are computed from the safety margin proportion, which can // optionally be specified by a registry value // ULONG gSafetyBuffer[2] = { 0L, 0L }; // // Number of times we have had to deny an allocation request even though we wanted // to allow it because we were flat out of packets. For debugging performance. // LARGE_INTEGER gStatOverflows[2] = {{ 0L, 0L }, {0L, 0L}}; // // Number of times we failed to allocated memory unexpectedly (i.e., when we had // not allocated up to the preset maximum size of our resource pool). Should only // occur if the host machine is actually out of non-paged memory (yikes!) // LARGE_INTEGER gStatFailures = { 0L, 0L }; // =========================================================================== // // PRIVATE PROTOTYPES // // =========================================================================== PNDIS_PACKET BrdgBufCommonGetNewPacket( IN NDIS_HANDLE Pool, OUT PPACKET_INFO *pppi ); PNDIS_PACKET BrdgBufGetNewCopyPacket( OUT PPACKET_INFO *pppi ); // Type of function to pass to BrgBufCommonGetPacket typedef PNDIS_PACKET (*PNEWPACKET_FUNC)(PPACKET_INFO*); PNDIS_PACKET BrdgBufCommonGetPacket( OUT PPACKET_INFO *pppi, IN PNEWPACKET_FUNC pNewPacketFunc, IN PBSINGLE_LIST_HEAD pCacheList, IN PNDIS_SPIN_LOCK ListLock ); BOOLEAN BrdgBufAssignQuota( IN QUOTA_PACKET_TYPE type, IN PADAPT pAdapt, IN BOOLEAN bCountAlloc ); VOID BrdgBufReleaseQuota( IN QUOTA_PACKET_TYPE type, IN PADAPT pAdapt ); // =========================================================================== // // INLINES / MACROS // // =========================================================================== // // Allocates a new wrapper packet // __forceinline PNDIS_PACKET BrdgBufGetNewWrapperPacket( OUT PPACKET_INFO *pppi ) { return BrdgBufCommonGetNewPacket( gWrapperPacketPoolHandle, pppi ); } // // Handles the special LOCAL_MINIPORT pseudo-pointer value // __forceinline PADAPTER_QUOTA QUOTA_FROM_ADAPTER( IN PADAPT pAdapt ) { SAFEASSERT( pAdapt != NULL ); if( pAdapt == LOCAL_MINIPORT ) { return &gMiniportQuota; } else { return &pAdapt->Quota; } } // // Switches from the packet type constant to an index // __forceinline UINT INDEX_FROM_TYPE( IN QUOTA_PACKET_TYPE type ) { SAFEASSERT( (type == BrdgQuotaCopyPacket) || (type == BrdgQuotaWrapperPacket) ); return (type == BrdgQuotaCopyPacket) ? 0 : 1; } // // Reinitializes a packet for reuse later // __forceinline VOID BrdgBufScrubPacket( IN PNDIS_PACKET pPacket, IN PPACKET_INFO ppi ) { // This scrubs NDIS's state NdisReinitializePacket( pPacket ); // Aggressively forget previous state to catch bugs NdisZeroMemory( ppi, sizeof(PACKET_INFO) ); ppi->pOwnerPacket = pPacket; } // // Decrements an adapter's used packet count // __forceinline VOID BrdgBufReleaseQuota( IN QUOTA_PACKET_TYPE type, IN PADAPT pAdapt ) { PADAPTER_QUOTA pQuota = QUOTA_FROM_ADAPTER(pAdapt); UINT index = INDEX_FROM_TYPE(type); NdisAcquireSpinLock( &gQuotaLock ); SAFEASSERT( pQuota->UsedPackets[index] > 0L ); pQuota->UsedPackets[index] --; NdisReleaseSpinLock( &gQuotaLock ); } // // Decrements the global usage count // __forceinline VOID BrdgBufCountDealloc( IN QUOTA_PACKET_TYPE type ) { UINT index = INDEX_FROM_TYPE(type); NdisAcquireSpinLock( &gQuotaLock ); SAFEASSERT( gUsedPackets[index] > 0L ); gUsedPackets[index]--; NdisReleaseSpinLock( &gQuotaLock ); } // =========================================================================== // // PUBLIC FUNCTIONS // // =========================================================================== VOID BrdgBufGetStatistics( PBRIDGE_BUFFER_STATISTICS pStats ) /*++ Routine Description: Retrieves our internal statistics on buffer management. Arguments: pStats The statistics structure to fill in Return Value: None --*/ { pStats->CopyPoolOverflows = gStatOverflows[0]; pStats->WrapperPoolOverflows = gStatOverflows[1]; pStats->AllocFailures = gStatFailures; pStats->MaxCopyPackets = gMaxPackets[0]; pStats->MaxWrapperPackets = gMaxPackets[1]; pStats->SafetyCopyPackets = gSafetyBuffer[0]; pStats->SafetyWrapperPackets = gSafetyBuffer[1]; NdisAcquireSpinLock( &gQuotaLock ); pStats->UsedCopyPackets = gUsedPackets[0]; pStats->UsedWrapperPackets = gUsedPackets[1]; NdisReleaseSpinLock( &gQuotaLock ); } PACKET_OWNERSHIP BrdgBufGetPacketOwnership( IN PNDIS_PACKET pPacket ) /*++ Routine Description: Returns a value indicating who owns this packet (i.e., whether we own this packet and it is from our copy pool, we own it and it's from our wrapper pool, or we don't own the packet at all). Arguments: pPacket The packet to examine Return Value: Ownership enumerated value --*/ { NDIS_HANDLE Pool = NdisGetPoolFromPacket(pPacket); if( Pool == gCopyPacketPoolHandle ) { return BrdgOwnCopyPacket; } else if ( Pool == gWrapperPacketPoolHandle ) { return BrdgOwnWrapperPacket; } return BrdgNotOwned; } VOID BrdgBufFreeWrapperPacket( IN PNDIS_PACKET pPacket, IN PPACKET_INFO ppi, IN PADAPT pQuotaOwner ) /*++ Routine Description: Frees a packet allocated from the wrapper pool and releases the quota previously assigned to the owning adapter Arguments: pPacket The packet ppi The packet's associated info block pQuotaOwner The adapter previously "charged" for this packet Return Value: None --*/ { SAFEASSERT( pQuotaOwner != NULL ); SAFEASSERT( pPacket != NULL ); SAFEASSERT( ppi != NULL ); // Free the packet BrdgBufFreeBaseWrapperPacket( pPacket, ppi ); // Account for this packet having been returned BrdgBufReleaseQuota( BrdgQuotaWrapperPacket, pQuotaOwner ); } PNDIS_PACKET BrdgBufGetBaseCopyPacket( OUT PPACKET_INFO *pppi ) /*++ Routine Description: Returns a new copy packet and associated info block from our pools WITHOUT CHECKING FOR QUOTA against any particular adapter This call is made to allocated copy packets for wrapping inbound packets before any target adapter has been identified. Arguments: pppi Receives the info block pointer (NULL if the alloc fails) Return Value: The new packet or NULL if the target adapter failed quota --*/ { PNDIS_PACKET pPacket; BOOLEAN bAvail = FALSE; NdisAcquireSpinLock( &gQuotaLock ); if( gUsedPackets[BrdgQuotaCopyPacket] < gMaxPackets[BrdgQuotaCopyPacket] ) { // There are packets still available in the pool. Grab one. bAvail = TRUE; gUsedPackets[BrdgQuotaCopyPacket]++; #if DBG // Keep track of the maximum used packets if( gMaxUsedPackets[BrdgQuotaCopyPacket] < gUsedPackets[BrdgQuotaCopyPacket] ) { gMaxUsedPackets[BrdgQuotaCopyPacket] = gUsedPackets[BrdgQuotaCopyPacket]; } #endif } else if( gUsedPackets[BrdgQuotaCopyPacket] == gMaxPackets[BrdgQuotaCopyPacket] ) { // We are at our limit. Hopefully this doesn't happen too often ExInterlockedAddLargeStatistic( &gStatOverflows[BrdgQuotaCopyPacket], 1L ); bAvail = FALSE; } else { // This should never happen; it means we are over our limit somehow SAFEASSERT( FALSE ); bAvail = FALSE; } NdisReleaseSpinLock( &gQuotaLock ); if( ! bAvail ) { // None available *pppi = NULL; return NULL; } pPacket = BrdgBufCommonGetPacket( pppi, BrdgBufGetNewCopyPacket, &gFreeCopyPacketList, &gFreeCopyPacketListLock ); if( pPacket == NULL ) { // Our allocation failed. Reverse the usage increment. BrdgBufCountDealloc( BrdgQuotaCopyPacket ); } return pPacket; } PNDIS_PACKET BrdgBufGetWrapperPacket( OUT PPACKET_INFO *pppi, IN PADAPT pAdapt ) /*++ Routine Description: Returns a new packet and associated info block from the wrapper pool, unless the owning adapter does not does pass a quota check Arguments: pppi Receives the info block pointer (NULL if the target adapter fails quota) pAdapt The adapter to be "charged" for this packet Return Value: The new packet or NULL if the target adapter failed quota --*/ { PNDIS_PACKET NewPacket = NULL; *pppi = NULL; if( BrdgBufAssignQuota(BrdgQuotaWrapperPacket, pAdapt, TRUE/*Count the alloc we are about to do*/) ) { // Passed quota. We can allocate. NewPacket = BrdgBufCommonGetPacket( pppi, BrdgBufGetNewWrapperPacket, &gFreeWrapperPacketList, &gFreeWrapperPacketListLock ); if( NewPacket == NULL ) { // We failed to allocate even though we haven't yet hit the ceiling on our // resource pool. This should only happen if we are physically out of non-paged // memory. // Reverse the adapter's quota bump BrdgBufReleaseQuota( BrdgQuotaWrapperPacket, pAdapt ); // Reverse the usage count in BrdgBufAssignQuota BrdgBufCountDealloc( BrdgQuotaWrapperPacket ); } } return NewPacket; } VOID BrdgBufReleaseBasePacketQuota( IN PNDIS_PACKET pPacket, IN PADAPT pAdapt ) /*++ Routine Description: Called to release the previously assigned cost of a wrapper packet. The packet provided can be any packet, even one we don't own. If we own the packet, we decrement the appropriate usage count in the adapter's quota structure. Arguments: pPacket The packet the indicated adapter is no longer referring to pAdapt The adapter no longer referring to pPacket Return Value: NULL --*/ { PACKET_OWNERSHIP Own = BrdgBufGetPacketOwnership(pPacket); // This gets called for any base packet, even ones we don't own. Just NOOP if we // don't own it. if( Own != BrdgNotOwned ) { BrdgBufReleaseQuota( (Own == BrdgOwnCopyPacket) ? BrdgQuotaCopyPacket : BrdgQuotaWrapperPacket, pAdapt ); } } BOOLEAN BrdgBufAssignBasePacketQuota( IN PNDIS_PACKET pPacket, IN PADAPT pAdapt ) /*++ Routine Description: Called to assign the cost of a base packet to an adapter, which is presumably attempting to construct a child wrapper packet that refers to the given base packet. A "cost" is assigned to pAdapt because by building a child wrapper packet that refers to the given base packet, pAdapt will cause it to not be disposed until it is done using it. It's OK for the input packet to be a packet we don't own; in that case, there is no cost to assign so we do nothing. Arguments: pPacket The base packet that pAdapt wishes to build a child wrapper packet referring to. pAdapt The adapter wishing to refer to pPacket Return Value: TRUE : The adapter is permitted to refer to the given base packet FALSE : The adapter did not pass qutoa and may not refer to the given base packet --*/ { PACKET_OWNERSHIP Own = BrdgBufGetPacketOwnership(pPacket); // We get called for any base packet, even if we don't own it. if( Own != BrdgNotOwned ) { return BrdgBufAssignQuota( (Own == BrdgOwnCopyPacket) ? BrdgQuotaCopyPacket : BrdgQuotaWrapperPacket, pAdapt, FALSE/*We aren't going to do an alloc for this quota bump*/); } else { return TRUE; } } PNDIS_PACKET BrdgBufCommonGetPacket( OUT PPACKET_INFO *pppi, IN PNEWPACKET_FUNC pNewPacketFunc, IN PBSINGLE_LIST_HEAD pCacheList, IN PNDIS_SPIN_LOCK ListLock ) /*++ Routine Description: Common processing for retrieving a new packet from either the copy pool or the wrapper pool Since we know how many packets we've allocated from each pool at all times, the only time this function should fail is if the host machine is physically out of memory. Arguments: pppi Receives the new info block (NULL if the alloc fails, which it shouldn't) pNewPacketFunc Function to call to alloc a packet if the cache is empty pCacheList Queue of cached packets that can be used to satisfy the alloc ListLock The lock to use when manipulating the cache queue Return Value: The newly allocated packet, or NULL if severe memory constraints cause the allocation to fail (this should be unusual) --*/ { PNDIS_PACKET pPacket; PPACKET_INFO ppi; PBSINGLE_LIST_ENTRY entry; // Try to get a packet out of our cache. entry = BrdgInterlockedRemoveHeadSingleList( pCacheList, ListLock ); if( entry == NULL ) { // Try to allocate a packet and info block from our underlying pools pPacket = (*pNewPacketFunc)( &ppi ); if( (pPacket == NULL) || (ppi == NULL) ) { // This should only occur if our host machine is actually out // of nonpaged memory; we should normally be able to allocate // up to our preset limit from our pools. ExInterlockedAddLargeStatistic( &gStatFailures, 1L ); } } else { ppi = CONTAINING_RECORD( entry, PACKET_INFO, List ); pPacket = ppi->pOwnerPacket; SAFEASSERT( pPacket != NULL ); } *pppi = ppi; return pPacket; } VOID BrdgBufFreeBaseCopyPacket( IN PNDIS_PACKET pPacket, IN PPACKET_INFO ppi ) /*++ Routine Description: Frees a packet allocated from the copy pool without quota adjustements. This is called directly from non-buffer-management code to free base packets because the cost for base packets is assigned and released directly with calls to BrdgBufBasePacketQuota. Arguments: pPacket The packet to free ppi Its info block to free Return Value: None --*/ { // If we're holding less than our cache amount, free the packet by putting it on the // cache list ULONG holding; PNDIS_BUFFER pBuffer = BrdgBufPacketHeadBuffer( pPacket ); SAFEASSERT( (ppi != NULL) && (pPacket != NULL) ); SAFEASSERT( ppi->pOwnerPacket == pPacket ); SAFEASSERT( pBuffer != NULL ); // Return this packet descriptor to its original state NdisAdjustBufferLength(pBuffer, MAX_PACKET_SIZE); NdisAcquireSpinLock( &gFreeCopyPacketListLock ); holding = BrdgQuerySingleListLength( &gFreeCopyPacketList ); if( holding < gSafetyBuffer[BrdgQuotaCopyPacket] ) { // Prep the packet for reuse // This blows away the buffer chain BrdgBufScrubPacket( pPacket, ppi ); // Put the buffer back on SAFEASSERT( BrdgBufPacketHeadBuffer(pPacket) == NULL ); NdisChainBufferAtFront( pPacket, pBuffer ); // Push the packet onto the list BrdgInsertHeadSingleList( &gFreeCopyPacketList, &ppi->List ); NdisReleaseSpinLock( &gFreeCopyPacketListLock ); } else { PVOID pBuf; UINT Size; NdisReleaseSpinLock( &gFreeCopyPacketListLock ); NdisQueryBufferSafe( pBuffer, &pBuf, &Size, NormalPagePriority ); // Free the packet, the packet info block and the copy buffer to the underlying pools NdisFreeBuffer( pBuffer ); NdisFreePacket( pPacket ); NdisFreeToNPagedLookasideList( &gPktInfoList, ppi ); if( pBuf != NULL ) { NdisFreeToNPagedLookasideList( &gCopyBufferList, pBuf ); } else { // Shouldn't be possible since the alloced memory is in kernel space SAFEASSERT( FALSE ); } } // Note the deallocation BrdgBufCountDealloc( BrdgQuotaCopyPacket ); } VOID BrdgBufFreeBaseWrapperPacket( IN PNDIS_PACKET pPacket, IN PPACKET_INFO ppi ) /*++ Routine Description: Frees a packet allocated from the wrapper pool without quota adjustements. This is called directly from non-buffer-management code to free base packets because the cost for base packets is assigned and released directly with calls to BrdgBufBasePacketQuota. Arguments: pPacket The packet to free ppi Its info block to free Return Value: None --*/ { // If we're holding less than our cache amount, free the packet by putting it on the // cache list ULONG holding; SAFEASSERT( (ppi != NULL) && (pPacket != NULL) ); SAFEASSERT( ppi->pOwnerPacket == pPacket ); NdisAcquireSpinLock( &gFreeWrapperPacketListLock ); holding = BrdgQuerySingleListLength( &gFreeWrapperPacketList ); if( holding < gSafetyBuffer[BrdgQuotaWrapperPacket] ) { // Prep the packet for reuse SAFEASSERT( BrdgBufPacketHeadBuffer(pPacket) == NULL ); BrdgBufScrubPacket( pPacket, ppi ); // Push the packet onto the list BrdgInsertHeadSingleList( &gFreeWrapperPacketList, &ppi->List ); NdisReleaseSpinLock( &gFreeWrapperPacketListLock ); } else { NdisReleaseSpinLock( &gFreeWrapperPacketListLock ); // Free the packet and packet info block to the underlying pools NdisFreePacket( pPacket ); NdisFreeToNPagedLookasideList( &gPktInfoList, ppi ); } // Note the deallocation BrdgBufCountDealloc( BrdgQuotaWrapperPacket ); } NDIS_STATUS BrdgBufChainCopyBuffers( IN PNDIS_PACKET pTargetPacket, IN PNDIS_PACKET pSourcePacket ) /*++ Routine Description: Allocates and chains buffer descriptors onto the target packet so it describes exactly the same areas of memory as the source packet Arguments: pTargetPacket Target packet pSourcePacket Source packet Return Value: Status of the operation. We have a limited-size pool of packet descriptors, so this operation can fail if we run out. --*/ { PNDIS_BUFFER pCopyBuffer, pCurBuf = BrdgBufPacketHeadBuffer( pSourcePacket ); NDIS_STATUS Status; SAFEASSERT( BrdgBufPacketHeadBuffer(pTargetPacket) == NULL ); // There must be something in the source packet! if( pCurBuf == NULL ) { SAFEASSERT( FALSE ); return NDIS_STATUS_RESOURCES; } while( pCurBuf != NULL ) { PVOID p; UINT Length; // Pull the virtual address and size out of the MDL being copied NdisQueryBufferSafe( pCurBuf, &p, &Length, NormalPagePriority ); if( p == NULL ) { BrdgBufUnchainCopyBuffers( pTargetPacket ); return NDIS_STATUS_RESOURCES; } // Is wacky to have a MDL describing no memory if( Length > 0 ) { // Get a new MDL from our pool and point it to the same address NdisAllocateBuffer( &Status, &pCopyBuffer, gWrapperBufferPoolHandle, p, Length ); if( Status != NDIS_STATUS_SUCCESS ) { THROTTLED_DBGPRINT(BUF, ("Failed to allocate a MDL in BrdgBufChainCopyBuffers: %08x\n", Status)); BrdgBufUnchainCopyBuffers( pTargetPacket ); return Status; } // Use the new MDL to chain to the target packet NdisChainBufferAtBack( pTargetPacket, pCopyBuffer ); } else { SAFEASSERT( FALSE ); } NdisGetNextBuffer( pCurBuf, &pCurBuf ); } return NDIS_STATUS_SUCCESS; } NTSTATUS BrdgBufDriverInit( ) /*++ Routine Description: Driver-load-time initialization routine. Arguments: None Return Value: Status of initialization. A return code != STATUS_SUCCESS causes the driver load to fail. Any event causing an error return code must be logged. --*/ { NDIS_STATUS Status; ULONG NumCopyPackets, ConsumptionPerCopyPacket, SizeOfPacket, i; ULONG MaxMemory = 0L, SafetyMargin = 0L; NTSTATUS NtStatus; // Initialize protective locks NdisAllocateSpinLock( &gFreeCopyPacketListLock ); NdisAllocateSpinLock( &gFreeWrapperPacketListLock ); NdisAllocateSpinLock( &gQuotaLock ); // Initialize cache lists BrdgInitializeSingleList( &gFreeCopyPacketList ); BrdgInitializeSingleList( &gFreeWrapperPacketList ); // Initialize look-aside lists for receive buffers and packet info blocks NdisInitializeNPagedLookasideList( &gCopyBufferList, NULL, NULL, 0, MAX_PACKET_SIZE, 'gdrB', 0 ); NdisInitializeNPagedLookasideList( &gPktInfoList, NULL, NULL, 0, sizeof(PACKET_INFO), 'gdrB', 0 ); // Initialize the miniport's quota information BrdgBufInitializeQuota( &gMiniportQuota ); // // Read in registry values. Substitute default values on failure. // NtStatus = BrdgReadRegDWord( &gRegistryPath, gMaxBufMemoryParameterName, &MaxMemory ); if( NtStatus != STATUS_SUCCESS ) { MaxMemory = DEFAULT_MAX_BUF_MEMORY; DBGPRINT(BUF, ( "Using DEFAULT maximum memory of %i\n", MaxMemory )); } NtStatus = BrdgReadRegDWord( &gRegistryPath, gSafetyMarginParameterName, &SafetyMargin ); if( NtStatus != STATUS_SUCCESS ) { SafetyMargin = DEFAULT_SAFETY_MARGIN; DBGPRINT(BUF, ( "Using DEFAULT safety margin of %i%%\n", SafetyMargin )); } // // Figure out the maximum number of packet descriptors in each pool we can allocate in order to // fit in the prescribed maximum memory space. // // For every copy packet, we allow ourselves GUESS_AVERAGE_FANOUT wrapper packets. // *Each* wrapper packet is allowed to consume GUESS_BUFFERS_PER_PACKET MDLs. // Given these relationships, we can calculate the number of copy packets that will fit in a given // memory footprint. The max for all other resources are set in relationship to that number. // SizeOfPacket = NdisPacketSize( PROTOCOL_RESERVED_SIZE_IN_PACKET ); ConsumptionPerCopyPacket = SizeOfPacket * (GUESS_AVERAGE_FANOUT + 1) + // Packet decriptor memory MAX_PACKET_SIZE + // Copy buffer memory sizeof(PACKET_INFO) * (GUESS_AVERAGE_FANOUT + 1) + // Packet info block memory sizeof(NDIS_BUFFER) * ((GUESS_AVERAGE_FANOUT * GUESS_BUFFERS_PER_PACKET) + 1); // MDL memory NumCopyPackets = MaxMemory / ConsumptionPerCopyPacket; // Allocate the packet pools NdisAllocatePacketPool( &Status, &gCopyPacketPoolHandle, NumCopyPackets, PROTOCOL_RESERVED_SIZE_IN_PACKET ); if( Status != NDIS_STATUS_SUCCESS ) { NdisDeleteNPagedLookasideList( &gCopyBufferList ); NdisDeleteNPagedLookasideList( &gPktInfoList ); NdisWriteEventLogEntry( gDriverObject, EVENT_BRIDGE_PACKET_POOL_CREATION_FAILED, 0L, 0L, NULL, sizeof(NDIS_STATUS), &Status ); DBGPRINT(BUF, ("Unable to allocate copy-packet pool: %08x\n", Status)); return STATUS_INSUFFICIENT_RESOURCES; } NdisAllocatePacketPool( &Status, &gWrapperPacketPoolHandle, GUESS_AVERAGE_FANOUT * NumCopyPackets, PROTOCOL_RESERVED_SIZE_IN_PACKET ); if( Status != NDIS_STATUS_SUCCESS ) { NdisDeleteNPagedLookasideList( &gCopyBufferList ); NdisDeleteNPagedLookasideList( &gPktInfoList ); NdisFreePacketPool( gCopyPacketPoolHandle ); NdisWriteEventLogEntry( gDriverObject, EVENT_BRIDGE_PACKET_POOL_CREATION_FAILED, 0L, 0L, NULL, sizeof(NDIS_STATUS), &Status ); DBGPRINT(BUF, ("Unable to allocate wrapper packet pool: %08x\n", Status)); return STATUS_INSUFFICIENT_RESOURCES; } // Allocate the buffer pools NdisAllocateBufferPool( &Status, &gCopyBufferPoolHandle, NumCopyPackets ); if( Status != NDIS_STATUS_SUCCESS ) { NdisDeleteNPagedLookasideList( &gCopyBufferList ); NdisDeleteNPagedLookasideList( &gPktInfoList ); NdisFreePacketPool( gCopyPacketPoolHandle ); NdisFreePacketPool( gWrapperPacketPoolHandle ); NdisWriteEventLogEntry( gDriverObject, EVENT_BRIDGE_BUFFER_POOL_CREATION_FAILED, 0L, 0L, NULL, sizeof(NDIS_STATUS), &Status ); DBGPRINT(BUF, ("Unable to allocate copy buffer pool: %08x\n", Status)); return STATUS_INSUFFICIENT_RESOURCES; } NdisAllocateBufferPool( &Status, &gWrapperBufferPoolHandle, GUESS_AVERAGE_FANOUT * GUESS_BUFFERS_PER_PACKET * NumCopyPackets ); if( Status != NDIS_STATUS_SUCCESS ) { NdisDeleteNPagedLookasideList( &gCopyBufferList ); NdisDeleteNPagedLookasideList( &gPktInfoList ); NdisFreePacketPool( gCopyPacketPoolHandle ); NdisFreePacketPool( gWrapperPacketPoolHandle ); NdisFreeBufferPool( gCopyBufferPoolHandle ); NdisWriteEventLogEntry( gDriverObject, EVENT_BRIDGE_BUFFER_POOL_CREATION_FAILED, 0L, 0L, NULL, sizeof(NDIS_STATUS), &Status ); DBGPRINT(BUF, ("Unable to allocate wrapper buffer pool: %08x\n", Status)); return STATUS_INSUFFICIENT_RESOURCES; } gInitedCopyBufferList = gInitedPktInfoList = TRUE; // Note the number of each packet type gMaxPackets[BrdgQuotaCopyPacket] = NumCopyPackets; gMaxPackets[BrdgQuotaWrapperPacket] = NumCopyPackets * GUESS_AVERAGE_FANOUT; // Calculate the safety buffer size in packets SAFEASSERT( SafetyMargin > 0L ); gSafetyBuffer[BrdgQuotaCopyPacket] = (gMaxPackets[BrdgQuotaCopyPacket] * SafetyMargin) / 100; gSafetyBuffer[BrdgQuotaWrapperPacket] = (gMaxPackets[BrdgQuotaWrapperPacket] * SafetyMargin) / 100; DBGPRINT(BUF, ( "Max memory usage of %d == %d copy packets, %d wrapper packets, %d copy-buffer space, %d/%d safety packets\n", MaxMemory, gMaxPackets[0], gMaxPackets[1], NumCopyPackets * MAX_PACKET_SIZE, gSafetyBuffer[0], gSafetyBuffer[1] )); // Pre-allocate the appropriate number of packets from each pool for perf. for( i = 0; i < gSafetyBuffer[BrdgQuotaCopyPacket]; i++ ) { PNDIS_PACKET pPacket; PPACKET_INFO ppi; pPacket = BrdgBufGetNewCopyPacket( &ppi ); // Should be impossible for this to fail if( (pPacket != NULL) && (ppi != NULL) ) { // Count the usage ourselves because we're not going through normal channels gUsedPackets[BrdgQuotaCopyPacket]++; // This should retain the packet in memory and decrement the usage count BrdgBufFreeBaseCopyPacket( pPacket, ppi ); } else { SAFEASSERT( FALSE ); } } for( i = 0; i < gSafetyBuffer[BrdgQuotaWrapperPacket]; i++ ) { PNDIS_PACKET pPacket; PPACKET_INFO ppi; pPacket = BrdgBufGetNewWrapperPacket( &ppi ); // Should be impossible for this to fail if( (pPacket != NULL) && (ppi != NULL) ) { // Count the usage ourselves because we're not going through normal channels gUsedPackets[BrdgQuotaWrapperPacket]++; // This should retain the packet in memory and decrement the usage count BrdgBufFreeBaseWrapperPacket( pPacket, ppi ); } else { SAFEASSERT( FALSE ); } } return STATUS_SUCCESS; } VOID BrdgBufCleanup() /*++ Routine Description: Unload-time orderly shutdown This function is guaranteed to be called exactly once Arguments: None Return Value: None --*/ { NDIS_HANDLE TmpHandle; if( gCopyPacketPoolHandle != NULL ) { PBSINGLE_LIST_ENTRY entry; TmpHandle = gCopyPacketPoolHandle; gCopyPacketPoolHandle = NULL; // Free all cached packets before freeing the pool entry = BrdgInterlockedRemoveHeadSingleList( &gFreeCopyPacketList, &gFreeCopyPacketListLock ); while( entry != NULL ) { PNDIS_PACKET pPacket; PPACKET_INFO ppi; PNDIS_BUFFER pBuffer; ppi = CONTAINING_RECORD( entry, PACKET_INFO, List ); pPacket = ppi->pOwnerPacket; SAFEASSERT( pPacket != NULL ); // Pull off the data buffer NdisUnchainBufferAtFront( pPacket, &pBuffer ); if( pBuffer != NULL ) { PVOID pBuf; UINT Size; NdisQueryBufferSafe( pBuffer, &pBuf, &Size, NormalPagePriority ); if( pBuf != NULL ) { // Ditch the data buffer NdisFreeToNPagedLookasideList( &gCopyBufferList, pBuf ); } // else can only fail under extreme memory pressure NdisFreeBuffer( pBuffer ); } else { // This packet should have a chained buffer SAFEASSERT( FALSE ); } NdisFreePacket( pPacket ); NdisFreeToNPagedLookasideList( &gPktInfoList, ppi ); entry = BrdgInterlockedRemoveHeadSingleList( &gFreeCopyPacketList, &gFreeCopyPacketListLock ); } // Free the pool now that all packets have been returned NdisFreePacketPool( TmpHandle ); } if( gWrapperPacketPoolHandle != NULL ) { PBSINGLE_LIST_ENTRY entry; TmpHandle = gWrapperPacketPoolHandle; gWrapperPacketPoolHandle = NULL; // Free all cached packets before freeing the pool entry = BrdgInterlockedRemoveHeadSingleList( &gFreeWrapperPacketList, &gFreeWrapperPacketListLock ); while( entry != NULL ) { PNDIS_PACKET pPacket; PPACKET_INFO ppi; ppi = CONTAINING_RECORD( entry, PACKET_INFO, List ); pPacket = ppi->pOwnerPacket; SAFEASSERT( pPacket != NULL ); NdisFreePacket( pPacket ); NdisFreeToNPagedLookasideList( &gPktInfoList, ppi ); entry = BrdgInterlockedRemoveHeadSingleList( &gFreeWrapperPacketList, &gFreeWrapperPacketListLock ); } // Free the pool now that all packets have been returned NdisFreePacketPool( TmpHandle ); } // The two lookaside lists should now be empty as well if( gInitedCopyBufferList ) { gInitedCopyBufferList = FALSE; NdisDeleteNPagedLookasideList( &gCopyBufferList ); } if( gInitedPktInfoList ) { gInitedPktInfoList = FALSE; NdisDeleteNPagedLookasideList( &gPktInfoList ); } if( gCopyBufferPoolHandle != NULL ) { TmpHandle = gCopyBufferPoolHandle; gCopyBufferPoolHandle = NULL; NdisFreeBufferPool( TmpHandle ); } if( gWrapperBufferPoolHandle != NULL ) { TmpHandle = gWrapperBufferPoolHandle; gWrapperBufferPoolHandle = NULL; NdisFreeBufferPool( TmpHandle ); } } // =========================================================================== // // PRIVATE FUNCTIONS // // =========================================================================== BOOLEAN BrdgBufAssignQuota( IN QUOTA_PACKET_TYPE type, IN PADAPT pAdapt, IN BOOLEAN bCountAlloc ) /*++ Routine Description: Determines whether a particular adapter should be permitted to allocate a new packet from a particular pool. Implements our quota algorithm. This can be called either to pre-approve an actual memory allocation or to check if an adapter should be permitted to refer to a base packet in constructing a child wrapper packet Arguments: type Type of packet pAdapt wishes to allocate or refer to pAdapt The adapter involved bCountAlloc Whether this is a check before an actual allocation. If it is, the global usage counts will be incremented within the gQuotaLock spin lock so everything is atomic Return Value: TRUE : The adapter is permitted to allocate / refer FALSE : The adapter is not permitted to allocate / refer --*/ { BOOLEAN rc; PADAPTER_QUOTA pQuota = QUOTA_FROM_ADAPTER(pAdapt); UINT index = INDEX_FROM_TYPE(type); // Freeze this value for the duration of the function ULONG numAdapters = gNumAdapters; NdisAcquireSpinLock( &gQuotaLock ); if( (numAdapters > 0) && (pQuota->UsedPackets[index] < (gMaxPackets[index] - gSafetyBuffer[index]) / numAdapters) ) { // This adapter is under its "fair share"; it can allocate if there are actually // any packets left! if( gUsedPackets[index] < gMaxPackets[index] ) { // There are packets left. This is the normal case. rc = TRUE; } else if( gUsedPackets[index] == gMaxPackets[index] ) { // This should be unusual; we've blown past our safety buffer. Hopefully this is // transitory. ExInterlockedAddLargeStatistic( &gStatOverflows[index], 1L ); rc = FALSE; } else { // This should never happen; it means we have allocated more than we should be able // to. SAFEASSERT( FALSE ); rc = FALSE; } } else { // This adapter is over its "fair share"; it can allocate only if there are more packets // left than the safety buffer calls for if( gMaxPackets[index] - gUsedPackets[index] > gSafetyBuffer[index] ) { rc = TRUE; } else { // We're too close to the wire; deny the request. rc = FALSE; } } if( rc ) { pQuota->UsedPackets[index]++; if( bCountAlloc ) { // The caller will allocate. Count the allocation before releasing the spin lock. gUsedPackets[index]++; #if DBG // Keep track of the maximum used packets if( gMaxUsedPackets[index] < gUsedPackets[index] ) { gMaxUsedPackets[index] = gUsedPackets[index]; } #endif } } NdisReleaseSpinLock( &gQuotaLock ); return rc; } PNDIS_PACKET BrdgBufGetNewCopyPacket( OUT PPACKET_INFO *pppi ) /*++ Routine Description: Allocates a brand new packet from the copy-packet pool. Every copy packet comes with an associated data buffer large enough to hold a complete Ethernet frame, so the allocation attempt has several steps Arguments: pppi The packet's info block, or NULL if the allocation fails Return Value: The new packet --*/ { PNDIS_PACKET pPacket; PPACKET_INFO ppi; // Try to allocate a packet and info block from our underlying pools pPacket = BrdgBufCommonGetNewPacket( gCopyPacketPoolHandle, &ppi ); if( (pPacket == NULL) || (ppi == NULL) ) { SAFEASSERT( (pPacket == NULL) && (ppi == NULL) ); } else { PVOID pBuf; // Allocate a copy buffer for the packet pBuf = NdisAllocateFromNPagedLookasideList( &gCopyBufferList ); if( pBuf == NULL ) { NdisFreePacket( pPacket ); NdisFreeToNPagedLookasideList( &gPktInfoList, ppi ); ppi = NULL; pPacket = NULL; } else { NDIS_STATUS Status; PNDIS_BUFFER pBuffer; // Allocate a buffer descriptor for the copy buffer NdisAllocateBuffer( &Status, &pBuffer, gCopyBufferPoolHandle, pBuf, MAX_PACKET_SIZE ); if( Status != NDIS_STATUS_SUCCESS ) { NdisFreePacket( pPacket ); NdisFreeToNPagedLookasideList( &gPktInfoList, ppi ); NdisFreeToNPagedLookasideList( &gCopyBufferList, pBuf ); ppi = NULL; pPacket = NULL; } else { SAFEASSERT( pBuffer != NULL ); NdisChainBufferAtFront( pPacket, pBuffer ); } } } *pppi = ppi; return pPacket; } PNDIS_PACKET BrdgBufCommonGetNewPacket( IN NDIS_HANDLE Pool, OUT PPACKET_INFO *pppi ) /*++ Routine Description: Common logic for allocating a brand new packet from either the wrapper pool or the copy pool. Every packet of any flavor comes with an associated info block. Both the alloc of the packet descriptor and the info block must succeed for the packet allocation to succeed. Arguments: Pool The pool to allocate from pppi The allocated info block or NULL if the alloc failed Return Value: The new packet or NULL if the alloc failed --*/ { PNDIS_PACKET pPacket; PPACKET_INFO ppi; NDIS_STATUS Status; // Try to allocate a new packet descriptor NdisAllocatePacket( &Status, &pPacket, Pool ); if( Status != NDIS_STATUS_SUCCESS ) { *pppi = NULL; return NULL; } SAFEASSERT( pPacket != NULL ); // Try to allocate a new packet info block ppi = NdisAllocateFromNPagedLookasideList( &gPktInfoList ); if( ppi == NULL ) { NdisFreePacket( pPacket ); pPacket = NULL; } else { ppi->pOwnerPacket = pPacket; } *pppi = ppi; return pPacket; }