windows-nt/Source/XPSP1/NT/net/nwlink/fwd/packets.c
2020-09-26 16:20:57 +08:00

565 lines
15 KiB
C

/*++
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+segmentsize<MaxRcvPktsPoolSize) {
RcvPktsPoolSize += segmentsize;
KeReleaseSpinLock (&AllocatorLock, oldIRQL);
}
else {
KeReleaseSpinLock (&AllocatorLock, oldIRQL);
return NULL;
}
}
// Allocate chunk of memory to hold segment header and buffers
segment = ExAllocatePoolWithTag (
NonPagedPool,
segmentsize,
FWD_POOL_TAG);
if (segment!=NULL) {
segment->PS_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; i<list->SL_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; i<FRAME_SIZE_VARIATIONS; i++) {
list = SegmentMap[i][i];
if (pktsize<=list->SL_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) && (listID<FRAME_SIZE_VARIATIONS));
KeAcquireSpinLock (&AllocatorLock, &oldIRQL);
list = SegmentMap[listID][listID];
ASSERT (list->SL_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_FreeCount<list->SL_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
}