/*++ Copyright (c) 1995 Microsoft Corporation Module Name: ntos\tdi\isn\fwd\packets.c Abstract: IPX Forwarder Driver packet allocator Author: Vadim Eydelman Revision History: --*/ #include "precomp.h" ULONG RcvPktsPerSegment = DEF_RCV_PKTS_PER_SEGMENT; ULONG MaxRcvPktsPoolSize =0; ULONG RcvPktsPoolSize = 0; KSPIN_LOCK AllocatorLock; const LONGLONG SegmentTimeout = -10i64*10000000i64; SEGMENT_LIST ListEther={1500}; SEGMENT_LIST ListTR4={4500}; SEGMENT_LIST ListTR16={17986}; PSEGMENT_LIST SegmentMap[FRAME_SIZE_VARIATIONS][FRAME_SIZE_VARIATIONS] = { {&ListEther, &ListEther, &ListEther}, {&ListEther, &ListTR4, &ListTR4}, {&ListEther, &ListTR4, &ListTR16} }; VOID AllocationWorker ( PVOID Context ); VOID SegmentTimeoutDpc ( PKDPC dpc, PVOID Context, PVOID SystemArgument1, PVOID SystemArgument2 ); /*++ ******************************************************************* C r e a t e S e g m e n t Routine Description: Allocates and initializes packet segment Arguments: list - segment list to which new segment will be added Return Value: Pointer to allocated segment, NULL if fails ******************************************************************* --*/ PPACKET_SEGMENT CreateSegment ( PSEGMENT_LIST list ) { KIRQL oldIRQL; NDIS_STATUS status; PPACKET_SEGMENT segment; ULONG segmentsize = list->SL_BlockCount*list->SL_BlockSize +FIELD_OFFSET(PACKET_SEGMENT,PS_Buffers); if (MaxRcvPktsPoolSize!=0) { // Check if this allocation would exceed the limit KeAcquireSpinLock (&AllocatorLock, &oldIRQL); if (RcvPktsPoolSize+segmentsizePS_SegmentList = list; segment->PS_FreeHead = NULL; segment->PS_BusyCount = 0; segment->PS_PacketPool = (NDIS_HANDLE)FWD_POOL_TAG; KeQuerySystemTime ((PLARGE_INTEGER)&segment->PS_FreeStamp); NdisAllocatePacketPoolEx ( &status, &segment->PS_PacketPool, list->SL_BlockCount, 0, IPXMacHeaderSize +FIELD_OFFSET (PACKET_TAG, PT_MacHeader)); if (status==NDIS_STATUS_SUCCESS) { IpxFwdDbgPrint (DBG_PACKET_ALLOC, DBG_WARNING, ("IpxFwd: CreateSegent pool: %x\n", segment->PS_PacketPool)); NdisAllocateBufferPool ( &status, &segment->PS_BufferPool, list->SL_BlockCount*2); if (status==NDIS_STATUS_SUCCESS) { PUCHAR bufferptr = segment->PS_Buffers; PNDIS_PACKET packetDscr; PNDIS_BUFFER bufferDscr; PPACKET_TAG packetTag; ULONG i; for (i=0; iSL_BlockCount; i++, bufferptr+=list->SL_BlockSize) { NdisAllocatePacket ( &status, &packetDscr, segment->PS_PacketPool); ASSERT (status==NDIS_STATUS_SUCCESS); packetTag = (PPACKET_TAG)packetDscr->ProtocolReserved; packetTag->PT_Segment = segment; packetTag->PT_Data = bufferptr; packetTag->PT_InterfaceReference = NULL; NdisAllocateBuffer ( &status, &packetTag->PT_MacHdrBufDscr, segment->PS_BufferPool, packetTag->PT_MacHeader, IPXMacHeaderSize); ASSERT (status==NDIS_STATUS_SUCCESS); NdisAllocateBuffer ( &status, &bufferDscr, segment->PS_BufferPool, bufferptr, list->SL_BlockSize); ASSERT (status==NDIS_STATUS_SUCCESS); if (bufferDscr) { NdisChainBufferAtFront (packetDscr, bufferDscr); } packetTag->PT_Next = segment->PS_FreeHead; segment->PS_FreeHead = packetTag; } IpxFwdDbgPrint (DBG_PACKET_ALLOC, DBG_WARNING, ("IpxFwd: Allocated packet segment %08lx for list %ld.\n", segment, list->SL_BlockSize)); return segment; } else { IpxFwdDbgPrint (DBG_PACKET_ALLOC, DBG_ERROR, ("IpxFwd: Failed to allocate buffer pool" " for new segment in list %ld.\n", list->SL_BlockSize)); } NdisFreePacketPool (segment->PS_PacketPool); } else { IpxFwdDbgPrint (DBG_PACKET_ALLOC, DBG_ERROR, ("IpxFwd: Failed to allocate packet pool" " for new segment in list %ld.\n", list->SL_BlockSize)); } ExFreePool (segment); } else { IpxFwdDbgPrint (DBG_PACKET_ALLOC, DBG_ERROR, ("IpxFwd: Failed to allocate new segment for list %ld.\n", list->SL_BlockSize)); } return NULL; } /*++ ******************************************************************* D e l e t e S e g m e n t Routine Description: Frees packet segment Arguments: segment - segment to free Return Value: None ******************************************************************* --*/ VOID DeleteSegment ( PPACKET_SEGMENT segment ) { PSEGMENT_LIST list = segment->PS_SegmentList; IpxFwdDbgPrint (DBG_PACKET_ALLOC, DBG_WARNING, ("IpxFwd: DeleteSegment entered. %d %x\n",segment->PS_BusyCount, segment->PS_PacketPool)); ASSERT (segment->PS_BusyCount == 0); // Free all NDIS packet and buffer descriptors first while (segment->PS_FreeHead!=NULL) { PNDIS_BUFFER bufferDscr; PPACKET_TAG packetTag = segment->PS_FreeHead; PNDIS_PACKET packetDscr = CONTAINING_RECORD (packetTag, NDIS_PACKET, ProtocolReserved); segment->PS_FreeHead = packetTag->PT_Next; ASSERT (packetTag->PT_MacHdrBufDscr!=NULL); NdisFreeBuffer (packetTag->PT_MacHdrBufDscr); NdisUnchainBufferAtFront (packetDscr, &bufferDscr); ASSERT (bufferDscr!=NULL); NdisFreeBuffer (bufferDscr); NdisFreePacket (packetDscr); } NdisFreeBufferPool (segment->PS_BufferPool); IpxFwdDbgPrint (DBG_PACKET_ALLOC, DBG_WARNING, ("IpxFwd: DeleteSegment pool: %x\n", segment->PS_PacketPool)); NdisFreePacketPool (segment->PS_PacketPool); // [pmay] Remove this -- for debugging only segment->PS_PacketPool = NULL; // Decrement memory used if we have a quota if (MaxRcvPktsPoolSize!=0) { KIRQL oldIRQL; ULONG segmentsize = list->SL_BlockCount*list->SL_BlockSize +FIELD_OFFSET(PACKET_SEGMENT,PS_Buffers); KeAcquireSpinLock (&AllocatorLock, &oldIRQL); RcvPktsPoolSize -= segmentsize; KeReleaseSpinLock (&AllocatorLock, oldIRQL); } ExFreePool (segment); IpxFwdDbgPrint (DBG_PACKET_ALLOC, DBG_WARNING, ("IpxFwd: Deleting segment %08lx in list %ld.\n", segment, list->SL_BlockSize)); } /*++ ******************************************************************* R e g i s t e r P a c k e t C o n s u m e r Routine Description: Registers a consumer (bound interface) of packets of the given size Arguments: pktsize - maximum size of packets needed listId - buffer to return packet list id where packets of required size are located Return Value: STATUS_SUCCESS - registration succeded STATUS_INSUFFICIENT_RESOURCES - not enogh resources to register ******************************************************************* --*/ NTSTATUS RegisterPacketConsumer ( IN ULONG pktsize, OUT INT *listID ) { NTSTATUS status=STATUS_SUCCESS; KIRQL oldIRQL; PSEGMENT_LIST list; INT i; LONG addRefCount = 1; KeAcquireSpinLock (&AllocatorLock, &oldIRQL); ASSERT (pktsize<=SegmentMap[FRAME_SIZE_VARIATIONS-1] [FRAME_SIZE_VARIATIONS-1]->SL_BlockSize); for (i=0; iSL_BlockSize) { list->SL_RefCount += 1; *listID = i; break; } } KeReleaseSpinLock (&AllocatorLock, oldIRQL); IpxFwdDbgPrint (DBG_PACKET_ALLOC, DBG_WARNING, ("IpxFwd: Registered packet consumer, pktsz: %ld, list: %ld.\n", pktsize, list->SL_BlockSize)); return status; } /*++ ******************************************************************* D e r e g i s t e r P a c k e t C o n s u m e r Routine Description: Deregisters a consumer (bound interface) of packets of the given size Arguments: listId - packet list id used by the consumer Return Value: None ******************************************************************* --*/ VOID DeregisterPacketConsumer ( IN INT listID ) { KIRQL oldIRQL; PSEGMENT_LIST list; ASSERT ((listID>=0) && (listIDSL_RefCount>0); list->SL_RefCount -= 1; KeReleaseSpinLock (&AllocatorLock, oldIRQL); IpxFwdDbgPrint (DBG_PACKET_ALLOC, DBG_WARNING, ("IpxFwd: Deregistered packet consumer, list: %ld.\n", list->SL_BlockSize)); } /*++ ******************************************************************* I n i t i a l i z e S e g m e n t L i s t Routine Description: Initializes list of packet segments Arguments: list - list to initalize Return Value: None ******************************************************************* --*/ VOID InitializeSegmentList( PSEGMENT_LIST list ) { InitializeListHead (&list->SL_Head); list->SL_FreeCount = 0; // Make sure we don't have any leftover larger than // the buffer size (kernel memory allocator // allocates full pages) list->SL_BlockCount = (ULONG) (ROUND_TO_PAGES ( list->SL_BlockSize*RcvPktsPerSegment +FIELD_OFFSET(PACKET_SEGMENT,PS_Buffers)) -FIELD_OFFSET(PACKET_SEGMENT,PS_Buffers)) /list->SL_BlockSize; list->SL_LowCount = list->SL_BlockCount/2; list->SL_RefCount = 0; list->SL_AllocatorPending = FALSE; list->SL_TimerDpcPending = FALSE; KeInitializeSpinLock (&list->SL_Lock); KeInitializeTimer (&list->SL_Timer); KeInitializeDpc (&list->SL_TimerDpc, SegmentTimeoutDpc, list); ExInitializeWorkItem (&list->SL_Allocator, AllocationWorker, list); } /*++ ******************************************************************* D e l e t e S e g m e n t L i s t Routine Description: Deletes list of packet segments Arguments: list - list to delete Return Value: None ******************************************************************* --*/ VOID DeleteSegmentList ( PSEGMENT_LIST list ) { KeCancelTimer (&list->SL_Timer); while (!IsListEmpty (&list->SL_Head)) { PPACKET_SEGMENT segment; segment = CONTAINING_RECORD (list->SL_Head.Blink, PACKET_SEGMENT, PS_Link); RemoveEntryList (&segment->PS_Link); DeleteSegment (segment); } } /*++ ******************************************************************* S e g m e n t T i m e o u t D p c Routine Description: Timer DPC that launches allocator worker to get rid of unused segments Arguments: Context - segment list to check for unused segments Return Value: None ******************************************************************* --*/ VOID SegmentTimeoutDpc ( PKDPC dpc, PVOID Context, PVOID SystemArgument1, PVOID SystemArgument2 ) { #define list ((PSEGMENT_LIST)Context) KIRQL oldIRQL; IpxFwdDbgPrint (DBG_PACKET_ALLOC, DBG_INFORMATION, ("IpxFwd: Segment timed out in list: %ld.\n", list->SL_BlockSize)); KeAcquireSpinLock (&list->SL_Lock, &oldIRQL); list->SL_TimerDpcPending = FALSE; if (!list->SL_AllocatorPending && (list->SL_FreeCount>=list->SL_BlockCount) && EnterForwarder ()) { list->SL_AllocatorPending = TRUE; KeReleaseSpinLock (&list->SL_Lock, oldIRQL); ExQueueWorkItem (&list->SL_Allocator, DelayedWorkQueue); } else { KeReleaseSpinLock (&list->SL_Lock, oldIRQL); } LeaveForwarder (); #undef list } /*++ ******************************************************************* A l l o c a t i o n W o r k e r Routine Description: Adds new segment or releases unused segments from the list depending on the free packet count and time that segments are not used Arguments: Context - packet list to process Return Value: None ******************************************************************* --*/ VOID AllocationWorker ( PVOID Context ) { #define list ((PSEGMENT_LIST)Context) KIRQL oldIRQL; PPACKET_SEGMENT segment = NULL; LONGLONG curTime; IpxFwdDbgPrint (DBG_PACKET_ALLOC, DBG_INFORMATION, ("IpxFwd: Allocating/scavenging segment(s) in list: %ld.\n", list->SL_BlockSize)); KeQuerySystemTime ((PLARGE_INTEGER)&curTime); KeAcquireSpinLock (&list->SL_Lock, &oldIRQL); list->SL_AllocatorPending = FALSE; if (list->SL_FreeCountSL_BlockCount) { KeReleaseSpinLock (&list->SL_Lock, oldIRQL); // First allocate a segment segment = CreateSegment (list); if (segment!=NULL) { KeAcquireSpinLock (&list->SL_Lock, &oldIRQL); InsertTailList (&list->SL_Head, &segment->PS_Link); list->SL_FreeCount += list->SL_BlockCount; if (!list->SL_TimerDpcPending && EnterForwarder ()) { list->SL_TimerDpcPending = TRUE; KeReleaseSpinLock (&list->SL_Lock, oldIRQL); KeSetTimer (&list->SL_Timer, *((PLARGE_INTEGER)&SegmentTimeout), &list->SL_TimerDpc); } else { KeReleaseSpinLock (&list->SL_Lock, oldIRQL); } } } else { // Make sure that there is either more than segment in the list // or there is one and no registered users if (!IsListEmpty (&list->SL_Head)) { // [pmay] Remove this -- for debugging purposes only. // //{ // LIST_ENTRY * pEntry = &list->SL_Head; // // IpxFwdDbgPrint (DBG_PACKET_ALLOC, DBG_WARNING, // ("IpxFwd: Scanning %x for possible segment deletion.\n",list)); // // while (pEntry->Flink != list->SL_Head.Flink) { // segment = CONTAINING_RECORD (pEntry->Flink, PACKET_SEGMENT, PS_Link); // IpxFwdDbgPrint (DBG_PACKET_ALLOC, DBG_WARNING, // ("IpxFwd: Segment: %x\n",segment)); // pEntry = pEntry->Flink; // } //} segment = CONTAINING_RECORD (list->SL_Head.Blink, PACKET_SEGMENT, PS_Link); // Check for all segments with no used blocks // except for the last one (delete event the last // one if there are no clients) while ((segment->PS_BusyCount==0) && ((list->SL_Head.Flink!=&segment->PS_Link) || (list->SL_RefCount<=0))) { LONGLONG timeDiff; // Check if it has not been used for long enough timeDiff = SegmentTimeout - (segment->PS_FreeStamp-curTime); if (timeDiff>=0) { // Delete the segment RemoveEntryList (&segment->PS_Link); list->SL_FreeCount -= list->SL_BlockCount; KeReleaseSpinLock (&list->SL_Lock, oldIRQL); DeleteSegment (segment); KeAcquireSpinLock (&list->SL_Lock, &oldIRQL); if (!IsListEmpty (&list->SL_Head)) { segment = CONTAINING_RECORD (list->SL_Head.Blink, PACKET_SEGMENT, PS_Link); continue; } } else { // Reschedule the timer otherwise if (!list->SL_TimerDpcPending && EnterForwarder ()) { list->SL_TimerDpcPending = TRUE; KeReleaseSpinLock (&list->SL_Lock, oldIRQL); KeSetTimer (&list->SL_Timer, *((PLARGE_INTEGER)&timeDiff), &list->SL_TimerDpc); goto ExitAllocator; // Spinlock is already released } } break; } // while } // if (IsListEmpty) KeReleaseSpinLock (&list->SL_Lock, oldIRQL); } ExitAllocator: LeaveForwarder (); #undef list }