diff options
Diffstat (limited to '')
-rw-r--r-- | private/ntos/dlc/dlcbuf.c | 2411 |
1 files changed, 2411 insertions, 0 deletions
diff --git a/private/ntos/dlc/dlcbuf.c b/private/ntos/dlc/dlcbuf.c new file mode 100644 index 000000000..c452dd33b --- /dev/null +++ b/private/ntos/dlc/dlcbuf.c @@ -0,0 +1,2411 @@ +/*++ + +Copyright (c) 1991 Microsoft Corporation +Copyright (c) 1991 Nokia Data Systems AB + +Module Name: + + dlcbuf.c + +Abstract: + + This module implements the DLC buffer pool manager and provides routines + to lock and unlock transmit buffers outside of the buffer pool + + DLC has a buffering scheme inherited from its progenitors right back to + the very first DOS implementation. We must share a buffer pool with an + application: the application allocates memory using any method it desires + and gives us a pointer to that memory and the length. We page-align the + buffer and carve it into pages. Any non page-aligned buffer at the start + or end of the buffer are discarded. + + Once DLC has a buffer pool defined, it allocates buffers in a fashion + similar to binary-buddy, or in a method that I shall call 'binary + spouse'. Blocks are initially all contained in page sized units. As + smaller blocks are required, a larger block is repeatedly split into 2 + until a i-block is generated where 2**i >= block size required. Unlike + binary-buddy, the binary-spouse method does not coalesce buddy blocks to + create larger buffers once split. Once divorced from each other, binary + spouse blocks are unlikely to get back together. + + BufferPoolAllocate is the function that single-handedly implements the + allocator mechanism. It basically handles 2 types of request in the same + routine: the first request is from BUFFER.GET where a buffer will be + returned to the app as a single buffer if we have a block available that + is large enough to satisfy the request. If the request cannot be satisfied + by a single block, we return a chain of smaller blocks. The second type of + request is from the data receive DPC processing where we have to supply a + single block to contain the data. Luckily, through the magic of MDLs, we + can return several smaller blocks linked together by MDLs which masquerade + as a single buffer. Additionally, we can create buffers larger than a + single page in the same manner. This receive buffer must later be handed + to the app in the same format as the buffer allocated by BUFFER.GET, so we + need to be able to view this kind of buffer in 2 ways. This accounts for + the complexity of the various headers and MDL descriptors which must be + applied to the allocated blocks + + Contents: + BufferPoolCreate + BufferPoolExpand + BufferPoolFreeExtraPages + DeallocateBuffer + AllocateBufferHeader + BufferPoolAllocate + BufferPoolDeallocate + BufferPoolDeallocateList + BufferPoolBuildXmitBuffers + BufferPoolFreeXmitBuffers + GetBufferHeader + BufferPoolDereference + BufferPoolReference + ProbeVirtualBuffer + AllocateProbeAndLockMdl + BuildMappedPartialMdl + UnlockAndFreeMdl + +Author: + + Antti Saarenheimo 12-Jul-1991 + +Environment: + + Kernel mode + +Revision History: + +--*/ + +#include <dlc.h> +#include <memory.h> +#include "dlcdebug.h" + +// +// LOCK/UNLOCK_BUFFER_POOL - acquires or releases per-buffer pool spin lock. +// Use kernel spin locking calls. Assumes variables are called "pBufferPool" and +// "irql" +// + +#define LOCK_BUFFER_POOL() KeAcquireSpinLock(&pBufferPool->SpinLock, &irql) +#define UNLOCK_BUFFER_POOL() KeReleaseSpinLock(&pBufferPool->SpinLock, irql) + +// +// data +// + +PDLC_BUFFER_POOL pBufferPools = NULL; + + +#define CHECK_FREE_SEGMENT_COUNT(pBuffer) + +/* +Enable this, if the free segment size checking fails: + +#define CHECK_FREE_SEGMENT_COUNT(pBuffer) CheckFreeSegmentCount(pBuffer) + +VOID +CheckFreeSegmentCount( + PDLC_BUFFER_HEADER pBuffer + ); + +VOID +CheckFreeSegmentCount( + PDLC_BUFFER_HEADER pBuffer + ) +{ + PDLC_BUFFER_HEADER pTmp; + UINT FreeSegments = 0; + + for (pTmp = (pBuffer)->FreeBuffer.pParent->Header.pNextChild; + pTmp != NULL; + pTmp = pTmp->FreeBuffer.pNextChild) { + if (pTmp->FreeBuffer.BufferState == BUF_READY) { + FreeSegments += pTmp->FreeBuffer.Size; + } + } + if (FreeSegments != (pBuffer)->FreeBuffer.pParent->Header.FreeSegments) { + DbgBreakPoint(); + } +} +*/ + + +/*++ + +DLC Buffer Manager +------------------ + +The buffer pool consists of virtual memory blocks, that must be allocated +by the application program. The buffer block descriptors are allocated +separately from the non-paged pool, because they must be safe from +any memory corruption done in by an application. + +The memory allocation strategy is binary buddy. All segments are +exponents of 2 between 256 and 4096. There is no official connection with +the system page size, but actually all segments are allocated within +a page and thus we minimize MDL sizes needed for them. Contiguous +buffer blocks decrease also the DMA overhead. The initial user buffer +is first split to its maximal binary components. The components +are split further in the run time, when the buffer manager runs +out of the smaller segments (eg. it splits a 1024 segment to one +512 segment and two 256 segments, if it run out of 256 segments and +there were no free 512 segments either). + +The clients of the buffer manager allocates buffer lists. They consists +of minimal number of binary segments. For example, a 1600 bytes buffer +request would return a list of 1024, 512 and 256 segments. The smallest +segment is the first and the biggest is the last. The application +program must deallocate all segments returned to it in the receive. +The validity of all deallocated segments is checked with a reservation list. + +Buffer Manager provides api commands: +- to initialize a buffer pool (pool constructor) +- to add locked and mapped virtual memory to the buffer +- to deallocate the buffer pool (destructor) +- to allocate a segment list (allocator) +- to deallocate a segment list (deallocator) +- to set Thresholds for the minimum buffer size + +--*/ + + +/*++ + + +MEMORY COMMITMENT + + +The commitement of buffer pools is a special service expecially for +the local busy state management of the link stations. By default +the uncommitted memory is the same as the free memory in the buffer +pool minus the minimum free Threshold, but when a link enters to a +busy state we know how much buffer space the link will need +to receive at least the next frame. Actually we will commit all +I- packets received in the local busy state. The local 'out of buffers' busy +state will be cleared only when there is enough uncommited space in the +buffer pool to receive all expected packets. We still indicate the local +busy state to user, because the flow control function can expand the buffer +pool, if it is necessary. We will just queue the clear local busy state +command to a command queue (even if we complete it immediately), +we don;t execute the queued command before there is enough uncommited space +to enable the link receive. + +The buffer space is committed by the size of all expected packets, +when the local busy state of a link is cleared. +All received packets are subracted from the commited buffer space +as far as the link has any committed memory. This may happen only +after a local busy states. + + +We will provide three macroes to + +BufGetPacketSize(PacketSize) - returns probable size of packet in buffers +BufGetUncommittedSpace(hBufferPool) - gets the current uncommited space +BufCommitBuffers(hBufferPool, BufferSize) - commits the given size +BufUncommitBuffers(hBufferPool, PacketSize) - uncommites a packet + +--*/ + +NTSTATUS +ProbeVirtualBuffer( + IN PUCHAR pBuffer, + IN LONG Length + ); + + +NTSTATUS +BufferPoolCreate( +#if DBG + IN PDLC_FILE_CONTEXT pFileContext, +#endif + IN PVOID pUserBuffer, + IN LONG MaxBufferSize, + IN LONG MinFreeSizeThreshold, + OUT HANDLE *pBufferPoolHandle, + OUT PVOID* AlignedAddress, + OUT PULONG AlignedSize + ) + +/*++ + +Routine Description: + + This routine performs initialization of the NT DLC API buffer pool. + It allocates the buffer descriptor and the initial header blocks. + +Arguments: + + pFileContext - pointer to DLC_FILE_CONTEXT structure + pUserBuffer - virtual base address of the buffer + MaxBufferSize - the maximum size of the buffer space + MinFreeSizeThreshold - the minimum free space in the buffer + pBufferPoolHandle - the parameter returns the handle of buffer pool, + the same buffer pool may be shared by several + open contexts of one or more dlc applications. + AlignedAddress - we return the page-aligned buffer pool address + AlignedSize - and the page-aligned buffer pool size + +Return Value: + + Returns NTSTATUS is a NT system call fails. + +--*/ + +{ + NTSTATUS status; + PDLC_BUFFER_POOL pBufferPool; + PVOID pAlignedBuffer; + INT i; + register PPACKET_POOL pHeaderPool; + + ASSUME_IRQL(DISPATCH_LEVEL); + + // + // page-align the buffer + // + + pAlignedBuffer = (PVOID)(((ULONG)pUserBuffer + PAGE_SIZE - 1) & -(LONG)PAGE_SIZE); + + // + // and make the length an integral number of pages + // + + MaxBufferSize = (MaxBufferSize - ((ULONG)pAlignedBuffer - (ULONG)pUserBuffer)) & -(LONG)PAGE_SIZE; + + // + // the buffer size must be at least one page (BufferSize will be 0 if + // this is not so) + // + + if (!MaxBufferSize) { + return DLC_STATUS_BUFFER_SIZE_EXCEEDED; + } + + // + // if the size of the buffer is less than the minimum lock size then we lock + // the entire buffer + // + + if (MaxBufferSize < MinFreeSizeThreshold) { + MinFreeSizeThreshold = MaxBufferSize; + } + + // + // allocate the DLC_BUFFER_POOL structure. This is followed by an array + // of pointers to buffer headers describing the pages in the buffer pool + // + + pBufferPool = ALLOCATE_ZEROMEMORY_DRIVER(sizeof(DLC_BUFFER_POOL) + + sizeof(PVOID) + * BYTES_TO_PAGES(MaxBufferSize) + ); + if (!pBufferPool) { + return DLC_STATUS_NO_MEMORY; + } + + // + // pHeaderPool is a pool of DLC_BUFFER_HEADER structures - one of these + // is used per page locked + // + + pHeaderPool = CREATE_BUFFER_POOL_FILE(DlcBufferPoolObject, + sizeof(DLC_BUFFER_HEADER), + 8 + ); + + if (!pHeaderPool) { + + FREE_MEMORY_DRIVER(pBufferPool); + + return DLC_STATUS_NO_MEMORY; + } + + // + // initialize the buffer pool structure + // + + pBufferPool->hHeaderPool = pHeaderPool; + + KeInitializeSpinLock(&pBufferPool->SpinLock); + + // + // UncommittedSpace is the space above the minimum free threshold in the + // locked region of the buffer pool. We set it to the negative of the + // minimum free threshold here to cause BufferPoolExpand to lock down + // the number of pages required to commit the minimum free threshold + // + + pBufferPool->UncommittedSpace = -MinFreeSizeThreshold; + + // + // MaxBufferSize is the size of the buffer pool rounded down to an integral + // number of pages + // + + pBufferPool->MaxBufferSize = (ULONG)MaxBufferSize; + + // + // BaseOffset is the page-aligned address of the buffer pool + // + + pBufferPool->BaseOffset = pAlignedBuffer; + + // + // MaxOffset is the last byte + 1 (?) in the buffer pool + // + + pBufferPool->MaxOffset = (PUCHAR)pAlignedBuffer + MaxBufferSize; + + // + // MaximumIndex is the number of pages that describe the buffer pool. This + // number is irrespective of the locked state of the pages + // + + pBufferPool->MaximumIndex = (ULONG)(MaxBufferSize / MAX_DLC_BUFFER_SEGMENT); + + // + // Link all unlocked pages to a link list. + // Put the last pages in the buffer to the end of the list. + // + + for (i = (INT)pBufferPool->MaximumIndex - 1; i >= 0; i--) { + pBufferPool->BufferHeaders[i] = pBufferPool->pUnlockedEntryList; + pBufferPool->pUnlockedEntryList = (PDLC_BUFFER_HEADER)&pBufferPool->BufferHeaders[i]; + } + for (i = 0; i < DLC_BUFFER_SEGMENTS; i++) { + InitializeListHead(&pBufferPool->FreeLists[i]); + } + InitializeListHead(&pBufferPool->PageHeaders); + + // + // We can now lock the initial page buffers for the buffer pool. + // The buffer pool allocation has been failed, if the procedure + // returns an error. + // + +#if DBG + status = BufferPoolExpand(pFileContext, pBufferPool); +#else + status = BufferPoolExpand(pBufferPool); +#endif + if (status != STATUS_SUCCESS) { + + // + // We must use the standard procedure for deallocation, + // because the memory locking may have succeeded partially. + // The derefence free all resources in the buffer pool. + // + + BufferPoolDereference( +#if DBG + pFileContext, +#endif + &pBufferPool + ); + } else { + + KIRQL irql; + + // + // Save the buffer pool handle to the link list of + // buffer pools + // + + ACQUIRE_DLC_LOCK(irql); + + pBufferPool->pNext = pBufferPools; + pBufferPools = pBufferPool; + + RELEASE_DLC_LOCK(irql); + + *pBufferPoolHandle = pBufferPool; + *AlignedAddress = pAlignedBuffer; + *AlignedSize = MaxBufferSize; + } + return status; +} + + +NTSTATUS +BufferPoolExpand( +#if DBG + IN PDLC_FILE_CONTEXT pFileContext, +#endif + IN PDLC_BUFFER_POOL pBufferPool + ) + +/*++ + +Routine Description: + + The function checks the minimum and maximum size Thresholds and + locks new pages or unlocks the extra pages and deallocates their + buffer headers. + The procedure uses the standard memory management functions + to lock, probe and map the pages. + + The MDL buffer is split to smaller buffers (256, 512, ... 4096). + The orginal buffer is split in the 4 kB even address (usually + page border or even with any page size) to minimize PFNs associated + with the MDLs (each MDL needs now only one PFN, to make + DMA overhead smaller and to save locked memory). + This procedure does not actually assume anything about the paging, + but it should work quite well with any paging implementation. + + This procedure MUST be called only from the synchronous code path and + all spinlocks unlocked, because of the page locking (the async + code in always on the DPC level and you cannot make pagefaults on + that level). + +Arguments: + + pBufferPool - handle of buffer pool data structure. + +Return Value: + + NTSTATUS + Success - STATUS_SUCCESS + Failure - DLC_STATUS_NO_MEMORY + +--*/ + +{ + NTSTATUS status = STATUS_SUCCESS; + PDLC_BUFFER_HEADER pBuffer; + KIRQL irql; + + ASSUME_IRQL(DISPATCH_LEVEL); + + LOCK_BUFFER_POOL(); + + // + // UncommittedSpace < 0 just means that we've encroached past the minimum + // free threshold and therefore we're in need of more buffer space (hence + // this function) + // + + if (((pBufferPool->UncommittedSpace < 0) || (pBufferPool->MissingSize > 0)) + && (pBufferPool->BufferPoolSize < pBufferPool->MaxBufferSize)) { + + UINT FreeSlotIndex; + + while ((pBufferPool->UncommittedSpace < 0) || (pBufferPool->MissingSize > 0)) { + + pBuffer = NULL; + + // + // if there are no more pages to lock or we can't allocate a header + // to describe the buffer then quit + // + + if (!pBufferPool->pUnlockedEntryList + || !(pBuffer = ALLOCATE_PACKET_DLC_BUF(pBufferPool->hHeaderPool))) { + status = DLC_STATUS_NO_MEMORY; + break; + } + + // + // We use a direct mapping to find the immediately + // the buffer headers. Unallocated pages are in a single entry + // link list in that table. We must remove the locked entry + // from the link list and save the buffer header address to the + // new slot. The offset of the entry defines also the free + // unlocked buffer in the buffer pool. + // I have used this funny structure to minimize header + // information for the unlocked virtual pages (you could have + // a huge virtual buffer pool with a very small overhead in DLC). + // + + FreeSlotIndex = (UINT)(((ULONG)pBufferPool->pUnlockedEntryList - (ULONG)pBufferPool->BufferHeaders) / sizeof(PVOID)); + + pBuffer->Header.BufferState = BUF_READY; + pBuffer->Header.pLocalVa = (PVOID)((PCHAR)pBufferPool->BaseOffset + FreeSlotIndex * MAX_DLC_BUFFER_SEGMENT); + pBufferPool->pUnlockedEntryList = pBufferPool->pUnlockedEntryList->pNext; + pBufferPool->BufferHeaders[FreeSlotIndex] = pBuffer; + + // + // Lock memory always outside the spin locks on 0 level. + // + + UNLOCK_BUFFER_POOL(); + + RELEASE_DRIVER_LOCK(); + + pBuffer->Header.pMdl = AllocateProbeAndLockMdl(pBuffer->Header.pLocalVa, + MAX_DLC_BUFFER_SEGMENT + ); + + ACQUIRE_DRIVER_LOCK(); + + LOCK_BUFFER_POOL(); + + if (pBuffer->Header.pMdl) { + pBuffer->Header.pGlobalVa = MmGetSystemAddressForMdl(pBuffer->Header.pMdl); + pBuffer->Header.FreeSegments = MAX_DLC_BUFFER_SEGMENT / MIN_DLC_BUFFER_SEGMENT; + status = AllocateBufferHeader( +#if DBG + pFileContext, +#endif + pBufferPool, + pBuffer, + MAX_DLC_BUFFER_SEGMENT / MIN_DLC_BUFFER_SEGMENT, + 0, // logical index within the page + 0 // page in the free page table + ); + } else { + MemoryLockFailed = TRUE; + status = DLC_STATUS_MEMORY_LOCK_FAILED; + +#if DBG + DbgPrint("DLC.BufferPoolExpand: AllocateProbeAndLockMdl(a=%x, l=%x) failed\n", + pBuffer->Header.pLocalVa, + MAX_DLC_BUFFER_SEGMENT + ); +#endif + + } + if (status != STATUS_SUCCESS) { + + // + // It failed => free MDL (if non-null) and + // restore the link list of available buffers + // + + if (pBuffer->Header.pMdl != NULL) { + UnlockAndFreeMdl(pBuffer->Header.pMdl); + } + pBufferPool->BufferHeaders[FreeSlotIndex] = pBufferPool->pUnlockedEntryList; + pBufferPool->pUnlockedEntryList = (PDLC_BUFFER_HEADER)&(pBufferPool->BufferHeaders[FreeSlotIndex]); + + DEALLOCATE_PACKET_DLC_BUF(pBufferPool->hHeaderPool, pBuffer); + + break; + } + +#if LLC_DBG + + CHECK_FREE_SEGMENT_COUNT(pBuffer->Header.pNextChild); + +#endif + + pBufferPool->FreeSpace += MAX_DLC_BUFFER_SEGMENT; + pBufferPool->UncommittedSpace += MAX_DLC_BUFFER_SEGMENT; + pBufferPool->BufferPoolSize += MAX_DLC_BUFFER_SEGMENT; + pBufferPool->MissingSize -= MAX_DLC_BUFFER_SEGMENT; + LlcInsertTailList(&pBufferPool->PageHeaders, pBuffer); + } + pBufferPool->MissingSize = 0; + + // + // We will return success, if at least the minimal amount + // memory was allocated. The initial pool size may be too + // big for the current memory constraints set by the + // operating system and actual available physical memory. + // + + if (pBufferPool->UncommittedSpace < 0) { + status = DLC_STATUS_NO_MEMORY; + } + } + + UNLOCK_BUFFER_POOL(); + + return status; +} + + +VOID +BufferPoolFreeExtraPages( +#if DBG + IN PDLC_FILE_CONTEXT pFileContext, +#endif + IN PDLC_BUFFER_POOL pBufferPool + ) + +/*++ + +Routine Description: + + The function checks the maximum Thresholds and + unlocks the extra pages and deallocates their buffer headers. + +Arguments: + + pBufferPool - handle of buffer pool data structure. + +Return Value: + + None. + +--*/ + +{ + PDLC_BUFFER_HEADER pBuffer; + KIRQL irql; + PDLC_BUFFER_HEADER pNextBuffer; + + ASSUME_IRQL(DISPATCH_LEVEL); + +/* + DbgPrint("MaxBufferSize: %x\n", pBufferPool->MaxBufferSize); + DbgPrint("Uncommitted size: %x\n", pBufferPool->UncommittedSpace); + DbgPrint("BufferPoolSize: %x\n", pBufferPool->BufferPoolSize); + DbgPrint("FreeSpace : %x\n", pBufferPool->FreeSpace); +*/ + + LOCK_BUFFER_POOL(); + + // + // Free the extra pages until we have enough free buffer space. + // + + pBuffer = (PDLC_BUFFER_HEADER)pBufferPool->PageHeaders.Flink; + + while ((pBufferPool->UncommittedSpace > MAX_FREE_SIZE_THRESHOLD) + && (pBuffer != (PVOID)&pBufferPool->PageHeaders)) { + + // + // We may free (unlock) only those buffers given, that have + // all buffers free. + // + + if ((UINT)(pBuffer->Header.FreeSegments == (MAX_DLC_BUFFER_SEGMENT / MIN_DLC_BUFFER_SEGMENT))) { + pNextBuffer = pBuffer->Header.pNextHeader; +#if DBG + DeallocateBuffer(pFileContext, pBufferPool, pBuffer); +#else + DeallocateBuffer(pBufferPool, pBuffer); +#endif + pBufferPool->FreeSpace -= MAX_DLC_BUFFER_SEGMENT; + pBufferPool->UncommittedSpace -= MAX_DLC_BUFFER_SEGMENT; + pBufferPool->BufferPoolSize -= MAX_DLC_BUFFER_SEGMENT; + pBuffer = pNextBuffer; + } else { + pBuffer = pBuffer->Header.pNextHeader; + } + } + + UNLOCK_BUFFER_POOL(); + +} + + +VOID +DeallocateBuffer( +#if DBG + IN PDLC_FILE_CONTEXT pFileContext, +#endif + IN PDLC_BUFFER_POOL pBufferPool, + IN PDLC_BUFFER_HEADER pBuffer + ) + +/*++ + +Routine Description: + + The routine unlinks all segments of a page from the free lists and + deallocates the data structures. + +Arguments: + + pBufferPool - handle of buffer pool data structure. + pBuffer - the deallocated buffer header + +Return Value: + + None + +--*/ + +{ + UINT FreeSlotIndex; + PDLC_BUFFER_HEADER pSegment, pNextSegment; + + // + // First we unlink the segments from the free lists and + // then free and unlock the data structs of segment. + // + + for (pSegment = pBuffer->Header.pNextChild; pSegment != NULL; pSegment = pNextSegment) { + pNextSegment = pSegment->FreeBuffer.pNextChild; + + // + // Remove the buffer from the free lists (if it is there) + // + + if (pSegment->FreeBuffer.BufferState == BUF_READY) { + LlcRemoveEntryList(pSegment); + } + +#if LLC_DBG + + else { + + // + // This else can be possible only if we are + // deleting the whole buffer pool (ref count=0) + // + + if (pBufferPool->ReferenceCount != 0) { + DbgPrint("Error: Invalid buffer state!"); + DbgBreakPoint(); + } + pSegment->FreeBuffer.pNext = NULL; + } + +#endif + + IoFreeMdl(pSegment->FreeBuffer.pMdl); + + DBG_INTERLOCKED_DECREMENT(AllocatedMdlCount); + + DEALLOCATE_PACKET_DLC_BUF(pBufferPool->hHeaderPool, pSegment); + } + + // + // Link the page to the free page list in buffer pool header + // + + FreeSlotIndex = (UINT)(((ULONG)pBuffer->Header.pLocalVa - (ULONG)pBufferPool->BaseOffset) / MAX_DLC_BUFFER_SEGMENT); + pBufferPool->BufferHeaders[FreeSlotIndex] = pBufferPool->pUnlockedEntryList; + pBufferPool->pUnlockedEntryList = (PDLC_BUFFER_HEADER)&(pBufferPool->BufferHeaders[FreeSlotIndex]); + UnlockAndFreeMdl(pBuffer->Header.pMdl); + LlcRemoveEntryList(pBuffer); + + DEALLOCATE_PACKET_DLC_BUF(pBufferPool->hHeaderPool, pBuffer); + +} + + +NTSTATUS +AllocateBufferHeader( +#if DBG + IN PDLC_FILE_CONTEXT pFileContext, +#endif + IN PDLC_BUFFER_POOL pBufferPool, + IN PDLC_BUFFER_HEADER pParent, + IN UCHAR Size, + IN UCHAR Index, + IN UINT FreeListTableIndex + ) + +/*++ + +Routine Description: + + The routine allocates and initializes a new buffer segment + and links it to the given free segment list. + +Arguments: + + pBufferPool - handle of buffer pool data structure. + pParent - the parent (page) node of this segemnt + Size - size of this segment in 256 byte units + Index - index of this segment in 256 byte units + FreeListTableIndex - log2(Size), (ie. 256 bytes=>0, etc.) + +Return Value: + + Returns NTSTATUS + Success - STATUS_SUCCESS + Failure - DLC_STATUS_NO_MEMORY + +--*/ + +{ + PDLC_BUFFER_HEADER pBuffer; + + ASSUME_IRQL(DISPATCH_LEVEL); + + if (!(pBuffer = ALLOCATE_PACKET_DLC_BUF(pBufferPool->hHeaderPool))) { + return DLC_STATUS_NO_MEMORY; + } + + pBuffer->FreeBuffer.pMdl = IoAllocateMdl((PUCHAR)pParent->Header.pLocalVa + + (UINT)Index * MIN_DLC_BUFFER_SEGMENT, + (UINT)Size * MIN_DLC_BUFFER_SEGMENT, + FALSE, // not used (no IRP) + FALSE, // we can't take this from user quota + NULL + ); + if (pBuffer->FreeBuffer.pMdl == NULL) { + + DEALLOCATE_PACKET_DLC_BUF(pBufferPool->hHeaderPool, pBuffer); + + return DLC_STATUS_NO_MEMORY; + } + + DBG_INTERLOCKED_INCREMENT(AllocatedMdlCount); + + pBuffer->FreeBuffer.pNextChild = pParent->Header.pNextChild; + pParent->Header.pNextChild = pBuffer; + pBuffer->FreeBuffer.pParent = pParent; + pBuffer->FreeBuffer.Size = Size; + pBuffer->FreeBuffer.Index = Index; + pBuffer->FreeBuffer.BufferState = BUF_READY; + pBuffer->FreeBuffer.FreeListIndex = (UCHAR)FreeListTableIndex; + + // + // Link the full page buffer to the first free list + // + + LlcInsertHeadList(&(pBufferPool->FreeLists[FreeListTableIndex]), pBuffer); + return STATUS_SUCCESS; +} + + +NTSTATUS +BufferPoolAllocate( +#if DBG + IN PDLC_FILE_CONTEXT pFileContext, +#endif + IN PDLC_BUFFER_POOL pBufferPool, + IN UINT BufferSize, + IN UINT FrameHeaderSize, + IN UINT UserDataSize, + IN UINT FrameLength, + IN UINT SegmentSizeIndex, + IN OUT PDLC_BUFFER_HEADER *ppBufferHeader, + OUT PUINT puiBufferSizeLeft + ) + +/*++ + +Routine Description: + + Function allocates the requested buffer (locked and mapped) from the + buffer pool and returns its MDL and descriptor table of user segments. + The returned buffer is actually the minimal combination of some segments + (256, 512, 1024, 2048, 4096). + + There is a header in each buffer segment. The size of the frame + header and the user data added to all frames are defined by the caller. + + The allocated segments will be linked in three level: + + - Segment headers will be linked in the reserved list to + be checked when the application program releases the buffers + back to pool. The actual segments cannot be used, because + they are located in unsafe user memory. + + - Segments are linked (from smaller to bigger) for application program, + this link list is used nowhere in the driver (because it is in ...) + + - MDLs of the same buffer list are linked for the driver + + The link lists goes from smaller segment to bigger, because + the last segment should be the biggest one in a transmit call + (it actually works very nice with 2 or 4 token ring frames). + + DON'T TOUCH the calculation of the segment size (includes operations + with BufferSize, ->Cont.DataLength and FirstHeaderSize), the logic is + very complex. The current code has been tested by hand using some + test values, and it seems to work (BufferSize = 0, 1, 0xF3, + FirstHeader = 0,2). + +Arguments: + + pBufferPool - handle of buffer pool data structure. + + BufferSize - the size of the actual data in the requested buffers. + This must really be the actual data. Nobody can know + the size of all segment headers beforehand. The buffer + size must include the frame header size added to the + first buffer in the list! + + FrameHeaderSize - the space reserved for the frame header depends on + buffer format (OS/2 or DOS) and if the data is read + contiguously or not. The buffer manager reserves four + bytes from the beginning of the first segment in frame + to link this frame to next frames. + + UserDataSize - buffer area reserved for user data (nobody uses this) + + FrameLength - the total frame length (may not be the same as + BufferSize, because the LAN and DLC headers may be + saved into the header. + + SegmentSizeIndex - the client may ask a number segments having a fixed + size (256, 512, ... 4096). + + ppBufferHeader - parameter returns the arrays of the user buffer + segments. The array is allocated in the end of this + buffer. This may include a pointer to a buffer pool, + that is already allocated. The old buffer list will + be linked behind the new buffers. + + puiBufferSizeLeft - returns the size of buffer space, that is not yet + allocated. The client may extend the buffer pool + and then continue the allocation of the buffers. + Otherwise you could not allocate more buffers than + defined by MinFreeSizeThreshold. + +Return Value: + + NTSTATUS + STATUS_SUCCESS + DLC_STATUS_NO_MEMORY - no available memory in the non paged pool + +--*/ + +{ + INT i, j, k; // loop indexes (three level loop) + INT LastIndex; // index of smallest allowed segment size. + INT LastAvailable; // Index of free list having biggest segments + UINT SegmentSize; // current segment size + PDLC_BUFFER_HEADER pPrev; + PMDL pPrevMdl; + PDLC_BUFFER_HEADER pNew; + PFIRST_DLC_SEGMENT pDlcBuffer, pLastDlcBuffer; + NTSTATUS Status = STATUS_SUCCESS; + KIRQL irql; + + static USHORT SegmentSizes[DLC_BUFFER_SEGMENTS] = { +#if defined(ALPHA) + 8192, +#endif + 4096, + 2048, + 1024, + 512, + 256 + }; + + ASSUME_IRQL(DISPATCH_LEVEL); + + // + // Link the old buffers behind the new ones. + // This is really sick: BufferGet is calling this second (or more) + // time after it has expanded the buffer pool for the new retry, + // we must search the last buffer header, because the extra + // buffer space is removed from it. + // + + pPrev = *ppBufferHeader; + if (pPrev != NULL) { + for (pNew = pPrev; + pNew->FrameBuffer.pNextSegment != NULL; + pNew = pNew->FrameBuffer.pNextSegment) { + ; // NOP + } + pLastDlcBuffer = (PFIRST_DLC_SEGMENT) + ( + (PUCHAR)pNew->FreeBuffer.pParent->Header.pGlobalVa + + (UINT)pNew->FreeBuffer.Index * MIN_DLC_BUFFER_SEGMENT + ); + } + + // + // the first frame size has been added to the total length + // (excluding the default header), but we must + // exclude the default buffer header. + // + + if (FrameHeaderSize > sizeof(NEXT_DLC_SEGMENT)) { + FrameHeaderSize -= sizeof(NEXT_DLC_SEGMENT); + } else { + FrameHeaderSize = 0; + } + + // + // The frame header must be included in the total buffer space + // just as any other stuff. We must add the maximum extra size + // to get all stuff to fit into buffers. + // + + BufferSize += MIN_DLC_BUFFER_SEGMENT - 1 + FrameHeaderSize; + + // + // Initialize the index variables for the loop + // + + if (SegmentSizeIndex == -1) { + i = 0; + LastIndex = DLC_BUFFER_SEGMENTS - 1; + SegmentSize = MAX_DLC_BUFFER_SEGMENT; + } else { + i = SegmentSizeIndex; + LastIndex = SegmentSizeIndex; + SegmentSize = SegmentSizes[SegmentSizeIndex]; + } + LastAvailable = 0; + + LOCK_BUFFER_POOL(); + + // + // Loop until we have found enough buffers for + // the given buffer space (any kind, but as few as possible) + // or for the given number of requested buffers. + // Initialize each new buffer. The frame header is a special case. + // We go from bigger segments to smaller ones. The last (and smallest) + // will be initialized as a frame header (if needed). + // + + for (; (i <= LastIndex) && BufferSize; i++) { + while (((SegmentSize - sizeof(NEXT_DLC_SEGMENT) - UserDataSize) < BufferSize) || (i == LastIndex)) { + + // + // Check if there are any buffers having the optimal size + // + + if (IsListEmpty(&pBufferPool->FreeLists[i])) { + + // + // Split a bigger segment to smallers. Link the + // extra segments to the free lists and return + // after that to the current size level. + // + + for (j = i; j > LastAvailable; ) { + j--; + if (!IsListEmpty(&pBufferPool->FreeLists[j])) { + + // + // Take the first available segment header in + // the free list + // + + pNew = LlcRemoveHeadList(&pBufferPool->FreeLists[j]); + + // + // Split segments until we reach the desired level. + // We leave every (empty) level between a new segment + // header (including the current level (= i). + // + + k = j; + do { + k++; + + // + // We must also split the orginal buffer header + // and its MDL. + // + + pNew->FreeBuffer.Size /= 2; + pNew->FreeBuffer.FreeListIndex++; + + // + // We create the new buffer header for + // the upper half of the old buffer segment. + // + + Status = AllocateBufferHeader( +#if DBG + pFileContext, +#endif + pBufferPool, + pNew->FreeBuffer.pParent, + pNew->FreeBuffer.Size, + (UCHAR)(pNew->FreeBuffer.Index + + pNew->FreeBuffer.Size), + (UINT)k + ); + + // + // We cannot stop on error, but we try to + // allocate several smaller segments before + // we will give up. + // + + if (Status != STATUS_SUCCESS) { + + // + // We couldn't split the buffer, return + // the current buffer back to its slot. + // + + pNew->FreeBuffer.Size *= 2; + pNew->FreeBuffer.FreeListIndex--; + LlcInsertHeadList(&pBufferPool->FreeLists[k-1], + pNew + ); + break; + } + } while (k != i); + break; + } + } + + // + // Did we succeed to split the bigger segments + // to smaller ones? + // + + if (IsListEmpty(&pBufferPool->FreeLists[i])) { + + // + // We have run out of bigger segments, let's try to + // use the smaller ones instead. Indicate, that + // there exist no bigger segments than current one. + // THIS BREAK STARTS A NEW LOOP WITH A SMALLER + // SEGMENT SIZE. + // + + LastAvailable = i; + break; + } + } else { + pNew = LlcRemoveHeadList(&pBufferPool->FreeLists[i]); + } + pDlcBuffer = (PFIRST_DLC_SEGMENT) + ((PUCHAR)pNew->FreeBuffer.pParent->Header.pGlobalVa + + (UINT)pNew->FreeBuffer.Index * MIN_DLC_BUFFER_SEGMENT); + + // + // The buffers must be chained together on three level: + // - using kernel Buffer headers (for driver) + // - by user pointer (for apps) + // - MDLs (for NDIS) + // + + if (pPrev == NULL) { + + // + // Frame header - initialize the list + // HACK-HACK!!!! + // + + pPrevMdl = NULL; + pDlcBuffer->Cont.pNext = NULL; + pLastDlcBuffer = pDlcBuffer; + } else { + pPrevMdl = pPrev->FrameBuffer.pMdl; + pDlcBuffer->Cont.pNext = (PNEXT_DLC_SEGMENT) + ((PUCHAR)pPrev->FrameBuffer.pParent->Header.pLocalVa + + (UINT)pPrev->FrameBuffer.Index * MIN_DLC_BUFFER_SEGMENT); + } + pBufferPool->FreeSpace -= SegmentSize; + pBufferPool->UncommittedSpace -= SegmentSize; + pNew->FrameBuffer.pNextFrame = NULL; + pNew->FrameBuffer.BufferState = BUF_USER; + pNew->FrameBuffer.pNextSegment = pPrev; + pNew->FrameBuffer.pParent->Header.FreeSegments -= pNew->FreeBuffer.Size; + +#if LLC_DBG + + if ((UINT)(MIN_DLC_BUFFER_SEGMENT * pNew->FreeBuffer.Size) != SegmentSize) { + DbgPrint("Invalid buffer size.\n"); + DbgBreakPoint(); + } + CHECK_FREE_SEGMENT_COUNT(pNew); + +#endif + + pPrev = pNew; + pDlcBuffer->Cont.UserOffset = sizeof(NEXT_DLC_SEGMENT); + pDlcBuffer->Cont.UserLength = (USHORT)UserDataSize; + pDlcBuffer->Cont.FrameLength = (USHORT)FrameLength; + pDlcBuffer->Cont.DataLength = (USHORT)(SegmentSize - sizeof(NEXT_DLC_SEGMENT) - UserDataSize); + + // + // Check if we have done it! + // Remember, that the buffer size have been round up/over to + // the next 256 bytes even adderss => we never go negative. + // + + BufferSize -= pDlcBuffer->Cont.DataLength; + + if (BufferSize < MIN_DLC_BUFFER_SEGMENT) { + pDlcBuffer->Cont.UserOffset += FrameHeaderSize; + pDlcBuffer->Cont.DataLength -= FrameHeaderSize; + + // + // The data must be read to the beginning of the + // buffer chain (eg. because of NdisTransferData). + // => the first buffer must be full and the last + // one must always be odd. The extra length + // in the partial MDL does not matter. + // + + BufferSize -= MIN_DLC_BUFFER_SEGMENT - 1; + pLastDlcBuffer->Cont.DataLength += BufferSize; + + BuildMappedPartialMdl( + pNew->FrameBuffer.pParent->Header.pMdl, + pNew->FrameBuffer.pMdl, + pNew->FrameBuffer.pParent->Header.pLocalVa + + pNew->FrameBuffer.Index * MIN_DLC_BUFFER_SEGMENT + + FrameHeaderSize + + UserDataSize + + sizeof(NEXT_DLC_SEGMENT), + pDlcBuffer->Cont.DataLength + ); + pNew->FrameBuffer.pMdl->Next = pPrevMdl; + + // + // The buffer headers must be procted (the flag prevents + // user to free them back buffer pool before we have + // indicated the chained receive frames to him). + // The linkage of frame headers will crash, if + // the header buffer is released before the frame + // was indicated! + // + + pNew->FrameBuffer.BufferState = BUF_RCV_PENDING; + BufferSize = 0; + break; + } else { + + // + // MDL must exclude the buffer header from the actual data. + // + + BuildMappedPartialMdl( + pNew->FrameBuffer.pParent->Header.pMdl, + pNew->FrameBuffer.pMdl, + pNew->FrameBuffer.pParent->Header.pLocalVa + + pNew->FrameBuffer.Index * MIN_DLC_BUFFER_SEGMENT + + UserDataSize + + sizeof(NEXT_DLC_SEGMENT), + pDlcBuffer->Cont.DataLength + ); + pNew->FrameBuffer.pMdl->Next = pPrevMdl; + } + } + SegmentSize /= 2; + } + if (BufferSize == 0) { + Status = STATUS_SUCCESS; + } else { + BufferSize -= (MIN_DLC_BUFFER_SEGMENT - 1); + + // + // The client, that is not running in DPC level may extend + // the buffer pool, if there is still available space left + // in the buffer pool + // + + if (pBufferPool->MaxBufferSize > pBufferPool->BufferPoolSize) { + + // + // We can expand the buffer pool, sometimes we must + // allocate new bigger segments, if the available + // smaller segments cannot satisfy the request. + // + + if ((LONG)BufferSize > pBufferPool->MissingSize) { + pBufferPool->MissingSize = (LONG)BufferSize; + } + Status = DLC_STATUS_EXPAND_BUFFER_POOL; + } else { + Status = DLC_STATUS_INADEQUATE_BUFFERS; + } + } + + UNLOCK_BUFFER_POOL(); + + *ppBufferHeader = pPrev; + *puiBufferSizeLeft = BufferSize; + + return Status; +} + + +NTSTATUS +BufferPoolDeallocate( + IN PDLC_BUFFER_POOL pBufferPool, + IN UINT BufferCount, + IN PLLC_TRANSMIT_DESCRIPTOR pBuffers + ) + +/*++ + +Routine Description: + + Function deallocates the requested buffers. It first checks + the user buffer in the page table and then adds its header to + the free list. + +Arguments: + + pBufferPool - handle of buffer pool data structure. + BufferCount - number of user buffers to be released + pBuffers - array of the user buffers + +Return Value: + + NTSTATUS + +--*/ + +{ + PDLC_BUFFER_HEADER pBuffer; + UINT i; + NTSTATUS status = STATUS_SUCCESS; + KIRQL irql; + + ASSUME_IRQL(PASSIVE_LEVEL); + + LOCK_BUFFER_POOL(); + + // + // Return all buffers + // + + for (i = 0; i < BufferCount; i++) { + pBuffer = GetBufferHeader(pBufferPool, pBuffers[i].pBuffer); + if (pBuffer && (pBuffer->FreeBuffer.BufferState == BUF_USER)) { + + register ULONG bufsize; + + // + // Set the buffer state READY and restore the modified + // size and offset fields in MDL + // + + pBuffer->FreeBuffer.BufferState = BUF_READY; + pBuffer->FreeBuffer.pParent->Header.FreeSegments += pBuffer->FreeBuffer.Size; + +#if LLC_DBG + if (pBuffer->FreeBuffer.pParent->Header.FreeSegments > 16) { + DbgPrint("Invalid buffer size.\n"); + DbgBreakPoint(); + } + CHECK_FREE_SEGMENT_COUNT(pBuffer); +#endif + + // + // a microscopic performance improvement: the compiler (x86 at + // least) generates the sequence of instructions to work out + // the buffer size (number of blocks * block size) TWICE, + // presumably because it can't assume that the structure hasn't + // changed between the 2 accesses? Anyhow, Nature abhors a + // vacuum, which is why my house is such a mess + // + + bufsize = pBuffer->FreeBuffer.Size * MIN_DLC_BUFFER_SEGMENT; + pBufferPool->FreeSpace += bufsize; + pBufferPool->UncommittedSpace += bufsize; + LlcInsertTailList(&pBufferPool->FreeLists[pBuffer->FreeBuffer.FreeListIndex], pBuffer); + } else { + + // + // At least one of the released buffers is invalid, + // may be already released, or it may not exist in + // the buffer pool at all + // + + status = DLC_STATUS_INVALID_BUFFER_ADDRESS; + } + } + + UNLOCK_BUFFER_POOL(); + + return status; +} + + +VOID +BufferPoolDeallocateList( + IN PDLC_BUFFER_POOL pBufferPool, + IN PDLC_BUFFER_HEADER pBufferList + ) + +/*++ + +Routine Description: + + Function deallocates the requested buffer list. + The buffer list may be circular or null terminated. + +Arguments: + + pBufferPool - handle of buffer pool data structure. + pBufferList - link list of user + +Return Value: + + None + +--*/ + +{ + PDLC_BUFFER_HEADER pBuffer, pNextBuffer, pFrameBuffer, pNextFrameBuffer; + KIRQL irql; + + if (pBufferList == NULL) { + return; + } + + LOCK_BUFFER_POOL(); + + // + // Return all buffers to the free lists. + // The segments are always linked to a null terminated link list. + // The frames are linked either circular or null terminated + // link list! + // + // Note: both next segment and frame pointers are overlayed with + // the pPrev and pNext pointers of the double linked free lists. + // + + pNextFrameBuffer = pBufferList; + do { + pBuffer = pFrameBuffer = pNextFrameBuffer; + pNextFrameBuffer = pFrameBuffer->FrameBuffer.pNextFrame; + do { + pNextBuffer = pBuffer->FrameBuffer.pNextSegment; + +#if LLC_DBG + + if (pBuffer->FreeBuffer.BufferState != BUF_USER + && pBuffer->FreeBuffer.BufferState != BUF_RCV_PENDING) { + DbgBreakPoint(); + } + if (pBuffer->FreeBuffer.pParent->Header.FreeSegments > 16) { + DbgPrint("Invalid buffer size.\n"); + DbgBreakPoint(); + } + CHECK_FREE_SEGMENT_COUNT(pBuffer); + +#endif + + // + // Set the buffer state READY and restore the modified + // size and offset fields in MDL + // + + pBuffer->FreeBuffer.BufferState = BUF_READY; + pBuffer->FreeBuffer.pParent->Header.FreeSegments += pBuffer->FreeBuffer.Size; + pBufferPool->FreeSpace += pBuffer->FreeBuffer.Size * MIN_DLC_BUFFER_SEGMENT; + pBufferPool->UncommittedSpace += pBuffer->FreeBuffer.Size * MIN_DLC_BUFFER_SEGMENT; + LlcInsertTailList(&pBufferPool->FreeLists[pBuffer->FreeBuffer.FreeListIndex], + pBuffer + ); + } while ( pBuffer = pNextBuffer ); + } while (pNextFrameBuffer && (pNextFrameBuffer != pBufferList)); + + UNLOCK_BUFFER_POOL(); + +} + + +NTSTATUS +BufferPoolBuildXmitBuffers( + IN PDLC_BUFFER_POOL pBufferPool, + IN UINT BufferCount, + IN PLLC_TRANSMIT_DESCRIPTOR pBuffers, + IN OUT PDLC_PACKET pPacket + ) + +/*++ + +Routine Description: + + Function build a MDL and buffer header list for a frame defined by + a scatter/gather array. All buffers outside the buffer pool + are probed and locked. All MDLs (the locked and ones for buffer pool) + are chained together. The buffer pool headers are also chained. + If any errors have been found the buffers are released using the + reverse function (BufferPoolFreeXmitBuffers). + + THIS FUNCTION HAS A VERY SPECIAL SPIN LOCKING DESIGN: + + First we free the global spin lock (and lower the IRQ level to the lowest), + Then, if the transmit is made from DLC buffers, we lock the + spin lock again using NdisSpinLock function, that saves and restores + the IRQL level, when it acquires and releases the spin lock. + + This all is done to minimize the spin locking overhead when we are + locking transmit buffers, that are usually DLC buffers or normal + user memory but not both. + +Arguments: + + pBufferPool - handle of buffer pool data structure, THIS MAY BE NULL!!!! + BufferCount - number of user buffers in the frame + pBuffers - array of the user buffers of the frame + pPacket - generic DLC packet used in transmit + +Return Value: + + NTSTATUS + +--*/ + +{ + PDLC_BUFFER_HEADER pBuffer, pPrevBuffer = NULL; + PMDL pMdl, pPrevMdl = NULL; + INT i; + NTSTATUS Status = STATUS_SUCCESS; + BOOLEAN FirstBuffer = TRUE; + UINT InfoFieldLength = 0; + BOOLEAN BufferPoolIsLocked = FALSE; // very neat optimization! + KIRQL irql; + + ASSUME_IRQL(PASSIVE_LEVEL); + + // + // The client may allocate buffer headers without any buffers! + // + + if (BufferCount != 0) { + + // + // walk the buffers in a reverse order to build the + // list in a convinient way. + // + + for (i = BufferCount - 1; i >= 0; i--) { + + if (pBuffers[i].cbBuffer == 0) { + continue; + } + + InfoFieldLength += pBuffers[i].cbBuffer; + + // + // Check first if the given address is in the same area as the + // buffer pool + // + + if (pBufferPool != NULL + && (ULONG)pBuffers[i].pBuffer >= (ULONG)pBufferPool->BaseOffset + && (ULONG)pBuffers[i].pBuffer < (ULONG)pBufferPool->MaxOffset) { + + // + // Usually all transmit buffers are either in the buffer + // pool or they are elsewhere in the user memory. + // This boolean flag prevents us to toggle the buffer + // pool spinlock for each transmit buffer segment. + // (and nt spinlock is slower than its critical section!!!) + // + + if (BufferPoolIsLocked == FALSE) { + + LOCK_BUFFER_POOL(); + + BufferPoolIsLocked = TRUE; + } + } + + // + // The previous check does not yet garantee, that the given buffer + // if really a buffer pool segment, but the buffer pool is + // is now unlocked if it was aboslutely outside of the buffer pool, + // GetBufferHeader- function requires, that the buffer pool + // is locked, when it is called! + // + + if (BufferPoolIsLocked + && (pBuffer = GetBufferHeader(pBufferPool, pBuffers[i].pBuffer)) != NULL) { + + // + // The provided buffer must be inside the allocated + // buffer, otherwise the user has corrupted its buffers. + // user offset within buffer + user length <= buffer + // length + // The buffer must be also be owned by the user + // + + if (((ULONG)pBuffers[i].pBuffer & (MIN_DLC_BUFFER_SEGMENT - 1)) + + (ULONG)pBuffers[i].cbBuffer + > (ULONG)(pBuffer->FreeBuffer.Size * MIN_DLC_BUFFER_SEGMENT) + || (pBuffer->FrameBuffer.BufferState & BUF_USER) == 0) { + + Status = DLC_STATUS_BUFFER_SIZE_EXCEEDED; + break; + } + + // + // The same DLC buffer may be referenced several times. + // Create a partial MDL for it and add the reference + // counter. + // + + if (pBuffer->FrameBuffer.BufferState & BUF_LOCKED) { + pMdl = IoAllocateMdl(pBuffers[i].pBuffer, + pBuffers[i].cbBuffer, + FALSE, // not used (no IRP) + FALSE, // can't charge from quota now + NULL // Do not link it to IRPs + ); + if (pMdl == NULL) { + Status = DLC_STATUS_NO_MEMORY; + break; + } + + DBG_INTERLOCKED_INCREMENT(AllocatedMdlCount); + + BuildMappedPartialMdl(pBuffer->FrameBuffer.pParent->Header.pMdl, + pMdl, + pBuffers[i].pBuffer, + pBuffers[i].cbBuffer + ); + pBuffer->FrameBuffer.ReferenceCount++; + + if (pBuffers[i].boolFreeBuffer) { + pBuffer->FrameBuffer.BufferState |= DEALLOCATE_AFTER_USE; + } + + } else { + + // + // Modify the MDL for this request, the length must + // not be bigger than the buffer length and the + // offset must be within the first 255 bytes of + // the buffer. Build also the buffer header list + // (i don't know why?) + // + + pMdl = pBuffer->FrameBuffer.pMdl; + + if ( + + ((UINT)(ULONG)pBuffers[i].pBuffer & (MIN_DLC_BUFFER_SEGMENT - 1)) + + + pBuffers[i].cbBuffer + + > (UINT)pBuffer->FrameBuffer.Size * MIN_DLC_BUFFER_SEGMENT) { + + Status = DLC_STATUS_INVALID_BUFFER_LENGTH; + break; + } + + pBuffer->FrameBuffer.pNextSegment = pPrevBuffer; + pBuffer->FrameBuffer.BufferState |= BUF_LOCKED; + pBuffer->FrameBuffer.ReferenceCount = 1; + + if (pBuffers[i].boolFreeBuffer) { + pBuffer->FrameBuffer.BufferState |= DEALLOCATE_AFTER_USE; + } + pPrevBuffer = pBuffer; + + // + // DLC applications may change the user length or + // buffer length of the frames given to them => + // we must reinitialize global buffer and its length + // + + BuildMappedPartialMdl(pBuffer->FrameBuffer.pParent->Header.pMdl, + pMdl, + pBuffers[i].pBuffer, + pBuffers[i].cbBuffer + ); + } + } else { + if (BufferPoolIsLocked == TRUE) { + + UNLOCK_BUFFER_POOL(); + + BufferPoolIsLocked = FALSE; + } + + // + // Setup the exception handler around the memory manager + // calls and clean up any extra data if this fails. + // + + pMdl = AllocateProbeAndLockMdl(pBuffers[i].pBuffer, pBuffers[i].cbBuffer); + if (pMdl == NULL) { + Status = DLC_STATUS_MEMORY_LOCK_FAILED; + +#if DBG + DbgPrint("DLC.BufferPoolBuildXmitBuffers: AllocateProbeAndLockMdl(a=%x, l=%x) failed\n", + pBuffers[i].pBuffer, + pBuffers[i].cbBuffer + ); +#endif + + break; + } + +#if LLC_DBG + cLockedXmitBuffers++; +#endif + + } + + // + // Chain all MDLs together + // + + pMdl->Next = pPrevMdl; + pPrevMdl = pMdl; + } + } + if (BufferPoolIsLocked == TRUE) { + + UNLOCK_BUFFER_POOL(); + + } + + pPacket->Node.pNextSegment = pPrevBuffer; + pPacket->Node.pMdl = pPrevMdl; + pPacket->Node.LlcPacket.InformationLength = (USHORT)InfoFieldLength; + + if (Status != STATUS_SUCCESS) { + + // + // Free all allocated buffer (but the last one because there + // was an error with it) + // + + BufferPoolFreeXmitBuffers(pBufferPool, pPacket); + } + return Status; +} + + +VOID +BufferPoolFreeXmitBuffers( + IN PDLC_BUFFER_POOL pBufferPool, + IN PDLC_PACKET pXmitNode + ) + +/*++ + +Routine Description: + + Function unlocks the xmit buffers that are not in the buffer pool. + The caller must use DeallocateBufferPool routine to + and deallocates and the buffers are returned back to the pool. + The function has to separate the MDLs of user buffers and + buffer pool MDLs. + +Arguments: + + pBufferPool - handle of buffer pool data structure. + pXmitNode - pointer to a structure, that includes the buffer header list, + MDL chain or it chains serveral transmits nodes and IRP together. + +Return Value: + + None + +--*/ + +{ + PDLC_BUFFER_HEADER pBuffer; + PDLC_BUFFER_HEADER pOtherBuffer = NULL; + PDLC_BUFFER_HEADER pNextBuffer = NULL; + PMDL pMdl, pNextMdl; + KIRQL irql; + +#if LLC_DBG + BOOLEAN FrameCounted = FALSE; +#endif + + // + // Free all DLC buffers and MDLs linked in the transmit node. + // MDL list may be larger than the buffer header list. + // + + if (pXmitNode != NULL) { + if (pBufferPool != NULL) { + + LOCK_BUFFER_POOL(); + + } + pBuffer = pXmitNode->Node.pNextSegment; + for (pMdl = pXmitNode->Node.pMdl; pMdl != NULL; pMdl = pNextMdl) { + pNextMdl = pMdl->Next; + pMdl->Next = NULL; + + // + // Unlock only those MDLs, that are outside the buffer pool. + // + + if ((pBuffer == NULL || pBuffer->FrameBuffer.pMdl != pMdl) + && (pOtherBuffer = GetBufferHeader(pBufferPool, MmGetMdlVirtualAddress(pMdl))) == NULL) { + +#if LLC_DBG + cUnlockedXmitBuffers++; +#endif + + UnlockAndFreeMdl(pMdl); + } else { + + // + // This pointer can be NULL only if the first condition + // if the previous 'if statement' was true => this cannot + // be an orginal buffer header. + // + + if (pOtherBuffer != NULL) { + + // + // This is not the first reference of the buffer pool + // segment, but a partial MDL created by a new + // reference to a buffer segment already in use. + // Free the paritial MDL and setup the buffer + // pointer for the next loop. + // + + pNextBuffer = pBuffer; + pBuffer = pOtherBuffer; + pOtherBuffer = NULL; + IoFreeMdl(pMdl); + DBG_INTERLOCKED_DECREMENT(AllocatedMdlCount); + } else if (pBuffer != NULL) { + + // + // This is the orginal refence of the buffer pool + // segment, we may advance also in the buffer header + // link list. + // + + pNextBuffer = pBuffer->FrameBuffer.pNextSegment; + } + + // + // The same DLC buffer may be referenced several times. + // Decrement the reference counter and free the + // list if this was the last released reference. + // + + pBuffer->FrameBuffer.ReferenceCount--; + if (pBuffer->FrameBuffer.ReferenceCount == 0) { + if (pBuffer->FrameBuffer.BufferState & DEALLOCATE_AFTER_USE) { + + // + // Set the buffer state READY and restore the modified + // size and offset fields in MDL + // + + pBuffer->FreeBuffer.BufferState = BUF_READY; + pBuffer->FreeBuffer.pParent->Header.FreeSegments += pBuffer->FreeBuffer.Size; + +#if LLC_DBG + if (pBuffer->FreeBuffer.pParent->Header.FreeSegments > 16) { + DbgPrint("Invalid buffer size.\n"); + DbgBreakPoint(); + } + CHECK_FREE_SEGMENT_COUNT(pBuffer); +#endif + + pBufferPool->FreeSpace += (UINT)pBuffer->FreeBuffer.Size * MIN_DLC_BUFFER_SEGMENT; + pBufferPool->UncommittedSpace += (UINT)pBuffer->FreeBuffer.Size * MIN_DLC_BUFFER_SEGMENT; + + LlcInsertTailList(&pBufferPool->FreeLists[pBuffer->FreeBuffer.FreeListIndex], pBuffer); + +#if LLC_DBG + if (FrameCounted == FALSE) { + FrameCounted = TRUE; + cFramesReleased++; + } +#endif + + } else { + pBuffer->FreeBuffer.BufferState = BUF_USER; + } + } + pBuffer = pNextBuffer; + } + } + if (pBufferPool != NULL) { + + UNLOCK_BUFFER_POOL(); + + } + } +} + + +PDLC_BUFFER_HEADER +GetBufferHeader( + IN PDLC_BUFFER_POOL pBufferPool, + IN PVOID pUserBuffer + ) + +/*++ + +Routine Description: + + Function returns the buffer pool header of the given + buffer in the user address space or NULL, if the given + address has no buffer. + +Arguments: + + pBufferPool - handle of buffer pool data structure. + pUserBuffer - DLC buffer address in user memory + +Return Value: + + Pointer of DLC buffer header + or NULL (if not found) + +--*/ + +{ + UINT PageTableIndex; + UINT IndexWithinPage; + PDLC_BUFFER_HEADER pBuffer; + + // + // The buffer pool may not exist, when we are transmitting frames. + // + + if (pBufferPool == NULL) { + return NULL; + } + + PageTableIndex = (UINT)(((ULONG)pUserBuffer - (ULONG)pBufferPool->BaseOffset) + / MAX_DLC_BUFFER_SEGMENT); + + // + // We simply discard the buffers outside the preallocated + // virtual buffer in user space. We must also check, + // that the buffer is really reserved and locked (ie. + // it is not in the free list of unlocked entries). + // Note, that the buffer pool base address have been aligned with + // the maximum buffer segment size. + // + + if (PageTableIndex >= (UINT)pBufferPool->MaximumIndex + || ((ULONG)pBufferPool->BufferHeaders[PageTableIndex] >= (ULONG)pBufferPool->BufferHeaders + && (ULONG)pBufferPool->BufferHeaders[PageTableIndex] < (ULONG)&pBufferPool->BufferHeaders[pBufferPool->MaximumIndex])) { + return NULL; + } + + IndexWithinPage = (UINT)(((ULONG)pUserBuffer & (MAX_DLC_BUFFER_SEGMENT - 1)) / MIN_DLC_BUFFER_SEGMENT); + + for ( + pBuffer = pBufferPool->BufferHeaders[PageTableIndex]->Header.pNextChild; + pBuffer != NULL; + pBuffer = pBuffer->FreeBuffer.pNextChild) { + + if (pBuffer->FreeBuffer.Index == (UCHAR)IndexWithinPage) { + + // + // We MUST not return a locked buffer, otherwise the app + // will corrupt the whole buffer pool. + // + + if ((pBuffer->FreeBuffer.BufferState & BUF_USER) == 0) { + return NULL; + } else { + return pBuffer; + } + } + } + return NULL; +} + + +VOID +BufferPoolDereference( +#if DBG + IN PDLC_FILE_CONTEXT pFileContext, +#endif + IN PDLC_BUFFER_POOL *ppBufferPool + ) + +/*++ + +Routine Description: + + This routine decrements the reference count of the buffer pool + and deletes it when the reference count hits to zero. + +Arguments: + + pFileContext - pointer to DLC_FILE_CONTEXT + pBufferPool - opaque handle of buffer pool data structure. + +Return Value: + + None + +--*/ + +{ + PDLC_BUFFER_HEADER pBufferHeader, pNextHeader; + KIRQL irql; + PDLC_BUFFER_POOL pBufferPool = *ppBufferPool; + + ASSUME_IRQL(ANY_IRQL); + + *ppBufferPool = NULL; + + if (pBufferPool == NULL) { + return; + } + + LOCK_BUFFER_POOL(); + + if (pBufferPool->ReferenceCount != 0) { + pBufferPool->ReferenceCount--; + } + if (pBufferPool->ReferenceCount == 0) { + + KIRQL Irql2; + + ACQUIRE_DLC_LOCK(Irql2); + + RemoveFromLinkList((PVOID*)&pBufferPools, pBufferPool); + + RELEASE_DLC_LOCK(Irql2); + + // + // The buffer pool does not exist any more !!! + // => we can remove the spin lock and free all resources + // + + UNLOCK_BUFFER_POOL(); + + for (pBufferHeader = (PDLC_BUFFER_HEADER)pBufferPool->PageHeaders.Flink; + !IsListEmpty(&pBufferPool->PageHeaders); + pBufferHeader = pNextHeader) { + + pNextHeader = pBufferHeader->Header.pNextHeader; +#if DBG + DeallocateBuffer(pFileContext, pBufferPool, pBufferHeader); +#else + DeallocateBuffer(pBufferPool, pBufferHeader); +#endif + + } + + DELETE_BUFFER_POOL_FILE(&pBufferPool->hHeaderPool); + + FREE_MEMORY_DRIVER(pBufferPool); + + } else { + +#if DBG + + DbgPrint("Buffer pool not released, reference count = %d\n", + pBufferPool->ReferenceCount + ); + +#endif + + UNLOCK_BUFFER_POOL(); + + } +} + + +NTSTATUS +BufferPoolReference( + IN HANDLE hExternalHandle, + OUT PVOID *phOpaqueHandle + ) + +/*++ + +Routine Description: + + This routine translates the the external buffer pool handle to + a local opaque handle (=void pointer of the structure) and + optioanlly checks the access rights of the current process to + the buffer pool memory. The probing may raise an exeption to + the IO- system, that will return error when this terminates. + The function also increments the reference count of the buffer pool. + +Arguments: + + hExternalHandle - buffer handle allocated from the handle table + phOpaqueHandle - opaque handle of buffer pool data structure + +Return Value: + + None + +--*/ + +{ + PDLC_BUFFER_POOL pBufferPool; + NTSTATUS Status; + KIRQL irql; + + ASSUME_IRQL(DISPATCH_LEVEL); + + ACQUIRE_DLC_LOCK(irql); + + for (pBufferPool = pBufferPools; pBufferPool != NULL; pBufferPool = pBufferPool->pNext) { + if (pBufferPool == hExternalHandle) { + break; + } + } + + RELEASE_DLC_LOCK(irql); + + if (pBufferPool == NULL) { + return DLC_STATUS_INVALID_BUFFER_HANDLE; + } + + // + // We must do the optional probing outside of the spinlocks + // and before we have incremented the reference count. + // We do only read probing, because it is simpler. + // + + RELEASE_DRIVER_LOCK(); + + Status = ProbeVirtualBuffer(pBufferPool->BaseOffset, pBufferPool->BufferPoolSize); + + ACQUIRE_DRIVER_LOCK(); + + if (Status == STATUS_SUCCESS) { + + LOCK_BUFFER_POOL(); + + pBufferPool->ReferenceCount++; + *phOpaqueHandle = (PVOID)pBufferPool; + + UNLOCK_BUFFER_POOL(); + + } + return Status; +} + + +NTSTATUS +ProbeVirtualBuffer( + IN PUCHAR pBuffer, + IN LONG Length + ) + +/*++ + +Routine Description: + + Tests an address range for accessability. Actually reads the first and last + DWORDs in the address range, and assumes the rest of the memory is paged-in. + +Arguments: + + pBuffer - address to test + Length - in bytes of region to check + +Return Value: + + NTSTATUS + Success - STATUS_SUCCESS + Failure - DLC_STATUS_MEMORY_LOCK_FAILED + +--*/ + +{ + NTSTATUS status = STATUS_SUCCESS; + + ASSUME_IRQL(PASSIVE_LEVEL); + + try { + ProbeAndReadUlong((PULONG)pBuffer); + ProbeAndReadUlong((PULONG)&pBuffer[(Length - sizeof(ULONG)) & -3L]); + } except(EXCEPTION_EXECUTE_HANDLER) { + +#if DBG + DbgPrint("DLC.ProbeVirtualBuffer: Error: Can't ProbeAndReadUlong a=%x, l=%x\n", + pBuffer, + Length + ); +#endif + + status = DLC_STATUS_MEMORY_LOCK_FAILED; + } + return status; +} + + +PMDL +AllocateProbeAndLockMdl( + IN PVOID UserBuffer, + IN UINT UserBufferLength + ) + +/*++ + +Routine Description: + + This function just allocates, probes, locks and optionally maps + any user buffer to kernel space. Returns NULL, if the operation + fails for any reason. + +Remarks: + + This routine can be called only below DPC level and when the user + context is known (ie. a spin locks must not be set!). + +Arguments: + + UserBuffer - user space address + UserBufferLength - length of that buffer is user space + +Return Value: + + PMDL - pointer if successful + NULL if not successful + +--*/ + +{ + PMDL pMdl; + + ASSUME_IRQL(PASSIVE_LEVEL); + + try { + pMdl = IoAllocateMdl(UserBuffer, + UserBufferLength, + FALSE, // not used (no IRP) + FALSE, // we don't charge the non-paged pool quota + NULL // Do not link it to IRP + ); + if (pMdl != NULL) { + +#if DBG + IF_DIAG(MDL_ALLOC) { + + PVOID caller, callerscaller; + + RtlGetCallersAddress(&caller, &callerscaller); + DbgPrint("A: pMdl=%#x caller=%#x caller's=%#x\n", + pMdl, + caller, + callerscaller + ); + } +#endif + + DBG_INTERLOCKED_INCREMENT(AllocatedMdlCount); + + MmProbeAndLockPages(pMdl, + UserMode, // Current user must have access! + IoModifyAccess + ); + + DBG_INTERLOCKED_ADD( + LockedPageCount, + +(ADDRESS_AND_SIZE_TO_SPAN_PAGES( + ((ULONG)pMdl->StartVa | pMdl->ByteOffset), + pMdl->ByteCount)) + ); + } + } except(EXCEPTION_EXECUTE_HANDLER) { + DBG_INTERLOCKED_INCREMENT(FailedMemoryLockings); + if (pMdl != NULL) { + IoFreeMdl(pMdl); + DBG_INTERLOCKED_DECREMENT(AllocatedMdlCount); + pMdl = NULL; + } + } + return pMdl; +} + + +VOID +BuildMappedPartialMdl( + IN PMDL pSourceMdl, + IN OUT PMDL pTargetMdl, + IN PVOID BaseVa, + IN ULONG Length + ) + +/*++ + +Routine Description: + + This function builds a partial MDL from a mapped source MDL. + The target MDL must have been initialized for the given size. + The target MDL cannot be used after the source MDL has been + unmapped. + +Remarks: + + MDL_PARTIAL_HAS_BEEN_MAPPED flag is not set in MdlFlag to + prevent IoFreeMdl to unmap the virtual address. + +Arguments: + + pSourceMdl - Mapped source MDL + pTargetMdl - Allocate MDL + BaseVa - virtual base address + Length - length of the data + +Return Value: + + None + +--*/ + +{ + ASSUME_IRQL(ANY_IRQL); + + if (Length) { + LlcMemCpy(&pTargetMdl[1], + &pSourceMdl[1], + (UINT)(sizeof(ULONG) * ADDRESS_AND_SIZE_TO_SPAN_PAGES(BaseVa, Length)) + ); + } + pTargetMdl->Next = NULL; + pTargetMdl->StartVa = (PVOID)PAGE_ALIGN(BaseVa); + pTargetMdl->ByteOffset = BYTE_OFFSET(BaseVa); + pTargetMdl->ByteCount = Length; + + // + // HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK-HACK + // + // The excellent NT memory manager doesn't provide any fast way to + // create temporary MDLs that will be deallocated before their + // actual source MDLs. + // We will never map this MDL, because its mapped orginal source mdl + // will be kept in memory until this (and its peers) have been + // deallocated. + // + + pTargetMdl->MdlFlags = (UCHAR)((pTargetMdl->MdlFlags & ~MDL_MAPPED_TO_SYSTEM_VA) + | MDL_SOURCE_IS_NONPAGED_POOL); + + pTargetMdl->MappedSystemVa = (PVOID)((PCHAR)MmGetSystemAddressForMdl(pSourceMdl) + + ((ULONG)BaseVa - (ULONG)MmGetMdlVirtualAddress(pSourceMdl))); +} + + +VOID +UnlockAndFreeMdl( + PMDL pMdl + ) + +/*++ + +Routine Description: + + This function unmaps (if not a partial buffer), unlocks and + and free a MDL. + + OK to call at DISPATCH_LEVEL + +Arguments: + + pMdl - pointer to MDL to free + +Return Value: + + None + +--*/ + +{ + ASSUME_IRQL(ANY_IRQL); + + DBG_INTERLOCKED_DECREMENT(AllocatedMdlCount); + DBG_INTERLOCKED_ADD(LockedPageCount, + -(ADDRESS_AND_SIZE_TO_SPAN_PAGES( + ((ULONG)((PMDL)pMdl)->StartVa | ((PMDL)pMdl)->ByteOffset), + (((PMDL)pMdl)->ByteCount))) + ); + + MmUnlockPages((PMDL)pMdl); + IoFreeMdl((PMDL)pMdl); + +#if DBG + + IF_DIAG(MDL_ALLOC) { + + PVOID caller, callerscaller; + + RtlGetCallersAddress(&caller, &callerscaller); + DbgPrint("F: pMdl=%#x caller=%#x caller's=%#x\n", + pMdl, + caller, + callerscaller + ); + } + +#endif + +} |