/*++

Copyright (c) 1991  Microsoft Corporation
Copyright (c) 1991  Nokia Data Systems

Module Name:

    vrdlcbuf.c

Abstract:

    The module implements the buffer management used by DOS DLC applications

    Contents:
        InitializeBufferPools
        CreateBufferPool
        DeleteBufferPool
        GetBuffers
        FreeBuffers
        CalculateBufferRequirement
        CopyFrame
        AllBuffersInPool

Author:

    Antti Saarenheimo (o-anttis) 26-DEC-1991

Notes:

    Originally, this code created a list of DOS buffers by keeping the segment
    constant, and updating the offset. For example, if a buffer pool was
    supplied starting at 0x1234:0000, buffers 0x100 bytes long, then the
    chain would be:

        1234:0000 -> 1234:0100 -> 1234:0200 -> ... -> 0000:0000

    But it turns out that some DOS DLC apps (Rumba) expect the offset to remain
    constant (at 0), and the segment to change(!). Thus, given the same buffer
    pool address, we would have a chain:

        1234:0000 -> 1244:0000 -> 1254:0000 -> ... -> 0000:0000

    As far as DOS apps are concerned, there is no difference, since the
    effective 20-bit address is the same.

    This is mainly done so that an app can take the USER_OFFSET field of a
    received buffer and glue it to the segment, without having to do any
    arithmetic

Revision History:

--*/

#include <nt.h>
#include <ntrtl.h>      // ASSERT, DbgPrint
#include <nturtl.h>
#include <windows.h>
#include <softpc.h>     // x86 virtual machine definitions
#include <vrdlctab.h>
#include <vdmredir.h>
#include <dlcapi.h>     // Official DLC API definition
#include <ntdddlc.h>    // IOCTL commands
#include <dlcio.h>      // Internal IOCTL API interface structures
#include "vrdlc.h"
#include "vrdebug.h"
#include "vrdlcdbg.h"

//
// defines
//

//
// BUFFER_2_SIZE - this is the size of the fixed part of a DOS receive Buffer 2.
// It just so happens that DOS and NT Buffer 2 (aka Next) are the same size
//

#define BUFFER_2_SIZE   sizeof(((PLLC_DOS_BUFFER)0)->Next)

//
// macros
//

//
// BUFFER_1_SIZE - return the size of the fixed part of a DOS receive Buffer 1.
// The size is dependent on whether the receive options specified contiguous or
// non-contiguous receive buffers. The size of a DOS Buffer 1 (of either type)
// is 4 bytes smaller than the equivalent NT Buffer 1 because the NEXT_FRAME
// field is absent
//

#define BUFFER_1_SIZE(contiguous)   ((contiguous) \
                                    ? sizeof(((PLLC_DOS_BUFFER)0)->Contiguous) \
                                    : sizeof(((PLLC_DOS_BUFFER)0)->NotContiguous))

//
// private prototypes
//

//
// public data
//

//
// private data
//

//
// DOS Buffer Pools - there can be one buffer pool per SAP. Protect access
// using critical section.
// There are 256 SAPs max, which breaks down into a maximum of 128 SAPs per
// adapter. We can accomodate a maximum of 2 adapters - one Token Ring (adapter
// 0) and one Ether Link (adapter 1)
//

DOS_DLC_BUFFER_POOL aBufferPools[DOS_DLC_MAX_SAPS * DOS_DLC_MAX_ADAPTERS];
CRITICAL_SECTION BufferSemaphore;


//
// functions
//

VOID
InitializeBufferPools(
    VOID
    )

/*++

Routine Description:

    Clears all buffer pools - sets structures to 0 - and initializes the buffer
    synchronization semaphore

Arguments:

    None.

Return Value:

    None.

--*/

{
    IF_DEBUG(DLC_BUFFERS) {
        DPUT("InitializeBufferPools\n");
    }

    InitializeCriticalSection(&BufferSemaphore);
    RtlZeroMemory(aBufferPools, sizeof(aBufferPools));
}


LLC_STATUS
CreateBufferPool(
    IN DWORD PoolIndex,
    IN DOS_ADDRESS dpBuffer,
    IN WORD PoolBlocks,
    IN WORD BufferSize
    )

/*++

Routine Description:

    The function initializes buffer pool for a DLC application.
    DOS DLC applications do not necessarily need to create the
    buffer pool immediately in DlcOpenSap (or DirOpenAdapter).
    We initialize the buffer pool for DOS memory mode using
    parallel pointers in flat and DOS side.

Arguments:

    PoolIndex   - SAP and adapter number (bit 0 defines 0 or 1 adapter)

    dpBuffer    - DOS pointer, space for the buffer segments. May be 0 in which
                  case the app maintains its own buffers, we just get to know
                  how many there are

    PoolBlocks  - number of 16 byte blocks which comprise the buffer pool.
                  If this is 0 then the default of 256 (*16 = 4096) is used

    BufferSize  - size of an individual buffer in bytes and an integral multiple
                  of 16. The minimum size is 80. If it is zero then the default
                  of 160 is used

Return Value:

    LLC_STATUS

        Success - LLC_STATUS_SUCCESS
                    Buffer pool for this SAP has been created

        Failure - LLC_STATUS_DUPLICATE_COMMAND
                    The buffer pool for this SAP already exists

                  LLC_STATUS_INVALID_BUFFER_LENGTH
                    The given buffer size is not a multiple of 16 or is less
                    than 80 bytes (default minimum buffer size)

                  LLC_STATUS_BUFFER_SIZE_EXCEEDED
                    The buffer pool isn't big enough to hold 1 buffer of the
                    requested size

--*/

{
    WORD BufferSizeInBlocks;
    WORD BufferCount;
    DWORD i;

    IF_DEBUG(DLC_BUFFERS) {
        DPUT4("CreateBufferPool(PoolIndex=%#x, dpBuffer=%#x, PoolBlocks=%d, BufferSize=%d)\n",
            PoolIndex, dpBuffer, PoolBlocks, BufferSize);
    }

    //
    // An app may reinitialize the buffer with DIR.MODIFY.OPEN.PARMS but the
    // command must fail, if there are already buffers in the pool. We should
    // also check if there is a pending receive command, but we cannot do it
    // without major changes in the receive handling architecture
    //

    if (aBufferPools[PoolIndex].BufferSize) {

        IF_DEBUG(DLC_BUFFERS) {
            DPUT1("CreateBufferPool: already have buffer pool for %#x\n", PoolIndex);
        }

        return LLC_STATUS_DUPLICATE_COMMAND;
    }

    //
    //  Use the default, if the orginal value is 0
    //

    if (BufferSize == 0) {
        BufferSize = 160;
    }
    if (PoolBlocks == 0) {
        PoolBlocks = 256;
    }

    //
    //  The buffer size must be at least 80 and an even 16 bytes
    //

    if ((BufferSize < 80) || (BufferSize % 16)) {
        return LLC_STATUS_INVALID_BUFFER_LENGTH;
    }

    BufferSizeInBlocks = BufferSize / 16;
    if (BufferSizeInBlocks > PoolBlocks) {

        IF_DEBUG(DLC_BUFFERS) {
            DPUT("CreateBufferPool: Error: BufferSizeInBlocks > PoolBlocks\n");
        }

        return LLC_STATUS_BUFFER_SIZE_EXCEEDED;
    }

    EnterCriticalSection(&BufferSemaphore);

    //
    // A DLC application may want to manage the buffer pool itself and to
    // provide the receive buffers with FreeBuffers, but the buffer size must
    // always be defined here.
    //

    aBufferPools[PoolIndex].BufferSize = BufferSize;
    aBufferPools[PoolIndex].dpBuffer = dpBuffer;    // may be 0!
    aBufferPools[PoolIndex].BufferCount = 0;

    //
    // if the app has actually given us a buffer to use then we initialize it
    // else the app must manage its own buffers; we just maintain the metrics.
    // Note that the app's buffer might be aligned any old way, but we have to
    // put up with it at the expense of speed
    //

    if (dpBuffer) {

        LPBYTE ptr32;

        BufferCount = PoolBlocks/BufferSizeInBlocks;

        //
        // if the number of buffers we can fit in our pool is zero then inform
        // the app that we can't proceed with this request and clear out the
        // information for this buffer pool
        //

        if (BufferCount == 0) {
            aBufferPools[PoolIndex].BufferSize = 0;
            aBufferPools[PoolIndex].dpBuffer = 0;
            aBufferPools[PoolIndex].BufferCount = 0;
            LeaveCriticalSection(&BufferSemaphore);
            return LLC_STATUS_BUFFER_SIZE_EXCEEDED;
        }

        aBufferPools[PoolIndex].BufferCount = BufferCount;
        aBufferPools[PoolIndex].MaximumBufferCount = BufferCount;

        //
        // convert the DOS address to a flat 32-bit pointer
        //

        ptr32 = (LPBYTE)DOS_PTR_TO_FLAT(dpBuffer);

        //
        // link the buffers together and initialize the headers. The headers
        // are only intialized with 2 pieces of info: the size of the buffer
        // and the pointer to the next buffer
        //

        aBufferPools[PoolIndex].dpBuffer = dpBuffer;

        //
        // update the segment only - leave the offset
        //

        dpBuffer += BufferSize / 16 * 65536;
        for (i = BufferCount; i; --i) {

            //
            // do we really need this? I don't think so: looking at the manual,
            // this field is to report size of data received, not size of
            // buffer, which we know anyway
            //

            //WRITE_WORD(((PLLC_DOS_BUFFER)ptr32)->Next.cbBuffer, BufferSize);

            //
            // if this is the last buffer then set its NextBuffer field to
            // NULL. All buffers get the size info
            //

            if (i - 1) {
                WRITE_DWORD(&((PLLC_DOS_BUFFER)ptr32)->Next.pNextBuffer, dpBuffer);

                //
                // update the segment only - leave the offset
                //

                dpBuffer += BufferSize / 16 * 65536;
                ptr32 += BufferSize;
            } else {
                WRITE_DWORD(&((PLLC_DOS_BUFFER)ptr32)->Next.pNextBuffer, NULL);
            }
        }

#if DBG
        IF_DEBUG(DLC_BUFFERS) {
            DumpDosDlcBufferPool(&aBufferPools[PoolIndex]);
        }
#endif

    }

    LeaveCriticalSection(&BufferSemaphore);
    return LLC_STATUS_SUCCESS;
}


VOID
DeleteBufferPool(
    IN DWORD PoolIndex
    )

/*++

Routine Description:

    The function deletes a buffer pool

Arguments:

    PoolIndex   - pool index based on SAP and adapter number

Return Value:

    None.

--*/

{
    IF_DEBUG(DLC_BUFFERS) {
        DPUT("DeleteBufferPool\n");
    }

    //
    // DLC.RESET for all adapters calls this 127 times
    //

    EnterCriticalSection(&BufferSemaphore);
    if (aBufferPools[PoolIndex].BufferSize != 0) {
        RtlZeroMemory(&aBufferPools[PoolIndex], sizeof(aBufferPools[PoolIndex]));
    }
    LeaveCriticalSection(&BufferSemaphore);
}


LLC_STATUS
GetBuffers(
    IN  DWORD PoolIndex,
    IN  WORD BuffersToGet,
    OUT DPLLC_DOS_BUFFER *pdpBuffer,
    OUT LPWORD pusBuffersLeft,
    IN  BOOLEAN PartialList,
    OUT PWORD BuffersGot OPTIONAL
    )

/*++

Routine Description:

    The function allocates DLC buffers. It will allocate buffers as a chain
    if >1 is requested. If PartialList is TRUE then will allocate as many
    buffers as are available up to BuffersToGet and return the number in
    BuffersGot

Arguments:

    PoolIndex       - SAP and adapter number
    BuffersToGet    - numbers of buffers to get. If this is 0, defaults to 1
    pdpBuffer       - the returned link list of LLC buffers
    pusBuffersLeft  - returned count of buffers left after this call
    PartialList     - TRUE if the caller wants a partial list
    BuffersGot      - pointer to returned number of buffers allocated

Return Value:

    LLC_STATUS
        Success - LLC_STATUS_SUCCESS
                    The requested number of buffers have been returned, or a
                    number of buffers less than the original request if
                    PartialList is TRUE

        Failure - LLC_STATUS_LOST_DATA_NO_BUFFERS
                    The request could not be satistfied - not enough buffers
                    in pool

                  LLC_STATUS_INVALID_STATION_ID
                    The request was mad to an invalid SAP

--*/

{
    PLLC_DOS_BUFFER pBuffer;
    PDOS_DLC_BUFFER_POOL pBufferPool = &aBufferPools[PoolIndex];
    LLC_STATUS status;
    WORD n;
    WORD bufferSize;

#if DBG
    DWORD numBufs = BuffersToGet;

    IF_DEBUG(DLC_BUFFERS) {
        DPUT2("GetBuffers(PoolIndex=%#02x, BuffersToGet=%d)\n", PoolIndex, BuffersToGet);
    }
#endif

    EnterCriticalSection(&BufferSemaphore);

    //
    // if the caller specified PartialList then return whatever we've got. If
    // whatever we've got is 0 then we'll default it to 1 and fail the allocation
    // since 0 is less than 1
    //

    if (PartialList) {
        if (pBufferPool->BufferCount < BuffersToGet) {
            BuffersToGet = pBufferPool->BufferCount;
        }
    }

    //
    // IBM DLC allows a default value of 1 to be used if the caller specified 0
    //

    if (!BuffersToGet) {
        ++BuffersToGet;
    }

    //
    // default the returned DOS buffer chain pointer to NULL
    //

    *pdpBuffer = 0;

    //
    // if there are no buffers defined then this is an erroneous request
    //

    if (pBufferPool->BufferSize) {

        //
        // calculate the size of the data part of the buffer. We put this value
        // in the LENGTH_IN_BUFFER field
        //

        bufferSize = pBufferPool->BufferSize
                   - (WORD)sizeof(pBuffer->Next);

        //
        // there may be no buffers left, in which case the next buffer pointer
        // (in DOS 16:16 format) will be 0 (0:0). If, on the other hand, it's
        // not 0 then we're in business: see if we can't allocate the buffers
        // requested
        //

        if (pBufferPool->dpBuffer && pBufferPool->BufferCount >= BuffersToGet) {

            pBuffer = (PLLC_DOS_BUFFER)DOS_PTR_TO_FLAT(pBufferPool->dpBuffer);
            *pdpBuffer = pBufferPool->dpBuffer;
            pBufferPool->BufferCount -= BuffersToGet;
            n = BuffersToGet;

            //
            // Eicon Access wants the size of the buffer in the buffer
            // when it is returned by BUFFER.GET. Oblige
            //

            WRITE_WORD(&pBuffer->Next.cbBuffer, bufferSize);

            //
            // we will return a chain of buffers, so we (nicely) terminate it
            // with a NULL for the last NextBuffer field. It doesn't say in
            // the lovely IBM Tech Ref whether this should be done, but its
            // probably the best thing to do. Because this buffer pool lives
            // in DOS memory, we have to use READ_POINTER and WRITE_FAR_POINTER
            // macros, lest we get an alignment fault on RISC
            //

            for (--BuffersToGet; BuffersToGet; --BuffersToGet) {
                pBuffer = (PLLC_DOS_BUFFER)READ_FAR_POINTER(&(pBuffer->pNext));
                if (pBuffer) {

                    //
                    // Eicon Access wants the size of the buffer in the buffer
                    // when it is returned by BUFFER.GET. Oblige
                    //

                    WRITE_WORD(&pBuffer->Next.cbBuffer, bufferSize);
                }
            }

            //
            // set the new buffer pool head
            //

            pBufferPool->dpBuffer = READ_DWORD(&pBuffer->pNext);

            //
            // terminate the chain
            //

            WRITE_FAR_POINTER(&pBuffer->pNext, NULL);

            status = LLC_STATUS_SUCCESS;

#if DBG
            IF_DEBUG(DLC_BUFFERS) {
                DumpDosDlcBufferChain(*pdpBuffer, numBufs ? numBufs : 1);
            }
#endif

        } else {

            //
            // if no buffers are obtained, the returned list is set to 0
            //


            status = LLC_STATUS_LOST_DATA_NO_BUFFERS;
            n = 0;
        }

        //
        // return the number of buffers left after this call. Works if we
        // allocated some or not
        //

        *pusBuffersLeft = pBufferPool->BufferCount;

    } else {

        //
        // bad SAP - no buffer pool for this one
        //

        status = LLC_STATUS_INVALID_STATION_ID;
        n = 0;
    }

    LeaveCriticalSection(&BufferSemaphore);

    //
    // if BuffersGot was specified then return the number of buffers allocated
    // and chained
    //

    if (ARGUMENT_PRESENT(BuffersGot)) {
        *BuffersGot = n;
    }

    IF_DEBUG(DLC_BUFFERS) {
        DPUT2("GetBuffers returning status=%x, BuffersLeft=%d\n", status, *pusBuffersLeft);
    }

    return status;
}


LLC_STATUS
FreeBuffers(
    IN DWORD PoolIndex,
    IN DPLLC_DOS_BUFFER dpBuffer,
    OUT LPWORD pusBuffersLeft
    )

/*++

Routine Description:

    Free a DOS buffer to a DLC buffer pool

Arguments:

    PoolIndex       - SAP and adapter number (bit0 defines 0 or 1 adapter)
    dpBuffer        - the released buffers (DOS pointer)
    pusBuffersLeft  - the number of buffers left after the free

Return Value:

--*/

{
    DPLLC_DOS_BUFFER dpBase;        // DOS pointer
    PLLC_DOS_BUFFER pNextBuffer;    // flat NT pointer
    PLLC_DOS_BUFFER pBuffer;        // flat NT pointer
    PDOS_DLC_BUFFER_POOL pBufferPool = &aBufferPools[PoolIndex];

#if DBG
    int n = 0;
#endif

    IF_DEBUG(DLC_BUFFERS) {
        DPUT2("FreeBuffers: PoolIndex=%x dpBuffer=%x\n", PoolIndex, dpBuffer);
    }

    if (pBufferPool->BufferSize == 0) {
        return LLC_STATUS_INVALID_STATION_ID;
    }

    EnterCriticalSection(&BufferSemaphore);

    dpBase = dpBuffer;
    pNextBuffer = pBuffer = DOS_PTR_TO_FLAT(dpBuffer);

    //
    // the manual says for BUFFER.FREE (p3-4):
    //
    //  "When the buffer is placed back in the buffer pool, bytes 4 and 5
    // (buffer length) of the buffer are set to zero."
    //
    // So, we oblige
    //

    WRITE_WORD(&pBuffer->Next.cbFrame, 0);
    if (pNextBuffer) {

        //
        // count the number of buffers being freed. Hopefully, the application
        // hasn't screwed over our terminating NULL pointer
        //

        while (pNextBuffer) {
            ++pBufferPool->BufferCount;

#if DBG
            ++n;
#endif

            pBuffer = pNextBuffer;
            pNextBuffer = (PLLC_DOS_BUFFER)READ_FAR_POINTER(&pBuffer->pNext);

            //
            // see above, about bytes 4 and 5
            //

            WRITE_WORD(&pBuffer->Next.cbFrame, 0);
        }

        //
        // put the freed chain at the head of the list, after linking the
        // buffer currently at the head of the list to the end of the freed
        // chain
        //

        WRITE_DWORD(&pBuffer->pNext, pBufferPool->dpBuffer);
        pBufferPool->dpBuffer = dpBase;

#if DBG
        IF_DEBUG(DLC_BUFFERS) {
            DumpDosDlcBufferChain(dpBuffer, n);
        }

    } else {

        DPUT("ERROR: App tried to free NULL buffer chain\n");
#endif

    }

    *pusBuffersLeft = pBufferPool->BufferCount;

    if (pBufferPool->BufferCount > pBufferPool->MaximumBufferCount) {
        pBufferPool->MaximumBufferCount = pBufferPool->BufferCount;
    }

    LeaveCriticalSection(&BufferSemaphore);

    return STATUS_SUCCESS;
}


WORD
CalculateBufferRequirement(
    IN UCHAR Adapter,
    IN WORD StationId,
    IN PLLC_BUFFER pFrame,
    IN LLC_DOS_PARMS UNALIGNED * pDosParms,
    OUT PWORD BufferSize
    )

/*++

Routine Description:

    Calculate the number of DOS buffers required to hold the data received into
    an NT buffer. We have to go through this laborious phase because we need to
    know ahead of time if we have enough DOS buffers into which we will receive
    an I-Frame.

    Also, the size of DOS buffers is fixed, but the size of NT receive frame
    buffers can vary depending on the size of the received frame, the options
    requested and the 'binary buddy' allocator algorithm in the DLC driver. You
    may think that since we specify to the driver the buffer pool size in the
    DLC.OPEN.SAP call that it would allocate buffers of the specified size.
    Well, you'd be wrong: the DLC driver ignores this information and creates
    a buffer pool which can dole out variable size buffers, making my life more
    difficult than it ought to be

Arguments:

    Adapter     - which adapter we're receiving from
    StationId   - the Station Id we're receiving on
    pFrame      - pointer to the received frame in NT buffer
    pDosParms   - pointer to the original DOS RECEIVE parameters
    BufferSize  - pointer to the returned DOS buffer size

Return Value:

    WORD

--*/

{
    //
    // pBufferPool points to the DOS buffer pool for this adapter/station ID
    //

    PDOS_DLC_BUFFER_POOL pBufferPool = &aBufferPools[GET_POOL_INDEX(Adapter, StationId)];

    //
    // dosUserLength is the USER_LENGTH value the DOS client requested when
    // the RECEIVE was submitted. This value may well be different than the
    // USER_LENGTH in the NT receive frame buffer
    //

    WORD dosUserLength = READ_WORD(&pDosParms->DosReceive.usUserLength);

    //
    // buffersRequired is the number of DOS buffers we need to allocate in order
    // to return the received frame. It will be at least 1
    //

    WORD buffersRequired = 1;

    //
    // dataSpace is the area in a DOS buffer available for data (after the
    // Buffer 1 or Buffer 2 header and the USER_LENGTH consideration)
    //

    WORD dataSpace;

    //
    // dataLeft is the amount of data in an NT buffer needing to be copied to
    // a DOS buffer
    //

    WORD dataLeft;

    //
    // calculate the number of DOS buffers required to hold the data frame
    // received into the NT buffer. Note that we can't simply use the size
    // of the received frame because we need the size of the buffer headers
    // and if the NT frame is larger than the DOS buffers then we may end
    // up with more DOS buffers required than NT buffers which in turn
    // results in more overhead which we have to factor in
    //

    WORD bufferSize = pBufferPool->BufferSize;

    IF_DEBUG(DLC_BUFFERS) {
        DPUT("CalculateBufferRequirement\n");
    }

    //
    // calculate the amount of space in a DOS buffer after the Buffer 1 structure
    // (contiguous or non-contiguous, smoking or non-smoking, ah, ah, ah ahh-haa)
    // The buffer size MUST be large enough to hold the Buffer 1 overhead. This
    // is a FACT
    //

    dataSpace = bufferSize
              - (BUFFER_1_SIZE(
                    pFrame->Contiguous.uchOptions
                        & (LLC_CONTIGUOUS_MAC | LLC_CONTIGUOUS_DATA)
                    )
                 + dosUserLength
                );

    //
    // if there is less data space available in the first DOS receive buffer
    // than received data in the NT buffer then our first NT buffer will be
    // mapped to >1 DOS buffers: a Buffer 1 and 1 or more Buffer 2s. This is
    // before we even get to any associated Buffer 2s in the NT receive frame.
    //
    // Also: if the LLC_BREAK option is specified in the receive parameters
    // then the first data buffer will contain the header information. Note:
    // we assume this can only be for NotContiguous data, else how would we
    // know the size of the header information?
    //

    if (pFrame->Contiguous.uchOptions & LLC_BREAK) {
        if (!(pFrame->Contiguous.uchOptions & (LLC_CONTIGUOUS_MAC | LLC_CONTIGUOUS_DATA))) {
            dataLeft = pFrame->NotContiguous.cbBuffer;
        }

#if DBG
        else {
            DPUT("CalculateBufferRequirement: Error: invalid options: BREAK && CONTIGUOUS???\n");
        }
#endif

    } else if (dataSpace < pFrame->Contiguous.cbBuffer) {
        dataLeft = pFrame->Contiguous.cbBuffer - dataSpace;
    } else {

        //
        // we have enough space in the DOS buffer to copy all the received data
        // and some more
        //

        dataSpace -= pFrame->Next.cbBuffer;
        dataLeft = 0;
    }

    //
    // if there is more data in the NT buffer than we can fit in a DOS buffer,
    // either because the buffer sizes are different or due to the DOS client
    // requesting the BREAK option, then generate Buffer 2 requirements
    //

    while (dataLeft) {
        ++buffersRequired;
        dataSpace = bufferSize - (BUFFER_2_SIZE + dosUserLength);
        if (dataLeft > dataSpace) {
            dataLeft -= dataSpace;
            dataSpace = 0;
        } else {
            dataSpace -= dataLeft;
            dataLeft = 0;
        }
    }

    //
    // if the NT received frame has any associated Buffer 2 structures then
    // calculate the additional buffer requirement. Again, the NT buffers may
    // be a different size(s) than the DOS buffers.
    //
    // At this point, dataSpace is the amount of remaining data area in the
    // previous DOS buffer - Buffer 1 or Buffer 2. Use this before we allocate
    // a new DOS buffer
    //

    for (pFrame = pFrame->pNext; pFrame; pFrame = pFrame->pNext) {
        if (pFrame->Next.cbBuffer > dataSpace) {
            dataLeft = pFrame->Next.cbBuffer - dataSpace;
            dataSpace = 0;
            while (dataLeft) {
                ++buffersRequired;
                dataSpace = bufferSize - (BUFFER_2_SIZE + dosUserLength);
                if (dataLeft > dataSpace) {
                    dataLeft -= dataSpace;
                    dataSpace = 0;
                } else {
                    dataSpace -= dataLeft;
                    dataLeft = 0;
                }
            }
        } else {

            //
            // we have enough space in the DOS buffer to copy all the received data
            // and some more
            //

            dataSpace -= pFrame->Next.cbBuffer;
            dataLeft = 0;
        }
    }

    IF_DEBUG(DLC_BUFFERS) {
        DPUT1("CalculateBufferRequirement: %d buffers required\n", buffersRequired);
    }

    //
    // set the output DOS buffer size and return the number of DOS buffers
    // required
    //

    *BufferSize = bufferSize;
    return buffersRequired;
}


LLC_STATUS
CopyFrame(
    IN PLLC_BUFFER pFrame,
    IN DPLLC_DOS_BUFFER DosBuffers,
    IN WORD UserLength,
    IN WORD BufferSize,
    IN DWORD Flags
    )

/*++

Routine Description:

    Copies a received NT frame into DOS buffers. We have previously calculated
    the DOS buffer requirement and allocated that requirement

    We may copy the entire received frame or only part of it. We can only copy
    partial frames if the frame is NOT an I-Frame

    Notes:  1. the DOS buffer manager returns the orginal DOS 16:16 buffer
            pointers, we must use those original pointers, when the buffers
            are linked to each other.

            2. We do NOT chain frames - DOS DLC cannot handle frames being
            chained, and there is nothing to be gained by us chaining them
            except that we reduce the number of completed READs. However,
            we still have to generate the same number of simulated hardware
            interrupts to the VDM

            3. Unlike DOS buffer pools, NT does not deal in buffers of a
            specific size. Rather, it allocates buffers from a pool based
            on a 'binary buddy' algorithm and the size of the data to be
            returned. Therefore, there is no correspondence between the
            size of DOS buffers (for a station) and the NT buffers which
            were used to receive the data

            4. We only copy the data in this routine - whether it is deferred
            data from some prior local busy state or current data is immaterial
            to this routine. Responsibility for managing current/deferred frames
            is left to the caller

Arguments:

    pFrame      - pointer to received frame in NT buffer(s)
    DosBuffers  - DOS pointer to chain of DOS receive buffers
    UserLength  - the USER_LENGTH value specified in the DOS RECEIVE
    BufferSize  - size of a DOS buffer
    Flags       - various flags:
                    CF_CONTIGUOUS
                        Set if this frame is contiguous

                    CF_BREAK
                        Set if the DOS client requested that the buffers be
                        broken into header, data

                    CF_PARTIAL
                        Set if we are copying a partial frame - ok for non
                        I-Frames

Return Value:

    LLC_STATUS
        LLC_STATUS_SUCCESS
            All data copied from NT buffer to DOS buffer(s)

        LLC_STATUS_LOST_DATA_INADEQUATE_SPACE
            A partial copy was performed. Some data ended up in DOS buffer(s),
            the rest is lost. Cannot be I-Frame!

--*/

{
    //
    // pDosBuffer - pointer to the DOS buffer which is usable in 32-bit mode
    //

    PLLC_DOS_BUFFER pDosBuffer = (PLLC_DOS_BUFFER)DOS_PTR_TO_FLAT(DosBuffers);

    //
    // dataSpace - amount of data space available in the current DOS buffer.
    // Initialize it for common Buffer 1 case
    //

    WORD dataSpace = BufferSize - (WORD)&(((PLLC_BUFFER)0)->Contiguous.pNextFrame);

    PBYTE pDosData;     // pointer to place in DOS buffer where we copy data TO
    PBYTE pNtData;      // corresponding place in NT buffer where we copy data FROM
    WORD headerLength;  // amount of data in headers
    WORD dataLeft;      // amount of data to be copied FROM the NT buffer
    WORD userOffset;    // offset of USER_SPACE
    WORD bufferLeft;    // amount of data left in current NT buffer
    WORD dataToCopy;    // amount of data to copy to DOS buffer
    WORD dataCopied;    // amount of data copied to Buffer 1/Buffer 2
    WORD frameLength;   // length of entire frame

    //
    // bufferOffset - used in generating the correct userOffset
    //

    WORD bufferOffset = LOWORD(DosBuffers);

    IF_DEBUG(DLC_BUFFERS) {
        DPUT5("CopyFrame: pFrame=%x DosBuffers=%x UserLength=%x Flags=%x pDosBuffer=%x\n",
                pFrame, DosBuffers, UserLength, Flags, pDosBuffer);
    }

    //
    // copy the first buffer. If the BREAK option is set then we only copy the
    // header part (ASSUMES NotContiguous)! NB: we KNOW that we can fit at least
    // this amount of data in the DOS buffer. Also: it is safe to use RtlCopyMemory ?
    //

    RtlCopyMemory(&pDosBuffer->Contiguous.cbFrame,
                  &pFrame->Contiguous.cbFrame,
                  (DWORD)&(((PLLC_BUFFER)0)->Contiguous.pNextFrame)
                  - (DWORD)&(((PLLC_BUFFER)0)->Contiguous.cbFrame)
                  );

    //
    // pDosData points to the area in the DOS buffer where the LAN header info
    // or data will go, depending on format
    //

    pDosData = &pDosBuffer->NotContiguous.cbLanHeader;

    //
    // if the CF_CONTIGUOUS flag is not set in the Flags parameter then this is
    // a NotContiguous frame. We must copy the header in 2 parts, because the
    // NT buffer contains the NEXT_FRAME field which the DOS buffer does not
    //

    if (!(Flags & CF_CONTIGUOUS)) {

        IF_DEBUG(DLC_BUFFERS) {
            DPUT("Buffer is NOT CONTIGUOUS\n");
        }

        RtlCopyMemory(pDosData,
                      &pFrame->NotContiguous.cbLanHeader,
                      (DWORD)&(((PLLC_BUFFER)0)->NotContiguous.usPadding)
                      - (DWORD)&(((PLLC_BUFFER)0)->NotContiguous.cbLanHeader)
                      );

        pDosData += (DWORD)&(((PLLC_BUFFER)0)->NotContiguous.usPadding)
                  - (DWORD)&(((PLLC_BUFFER)0)->NotContiguous.cbLanHeader);

        dataSpace -= (WORD)&(((PLLC_BUFFER)0)->NotContiguous.usPadding)
                   - (WORD)&(((PLLC_BUFFER)0)->NotContiguous.cbLanHeader);
        userOffset = (WORD)&(((PLLC_DOS_BUFFER)0)->NotContiguous.auchDlcHeader)
                   + sizeof(((PLLC_DOS_BUFFER)0)->NotContiguous.auchDlcHeader);

        //
        // sanity check
        //

        ASSERT(userOffset == 58);

        //
        // amount of data in headers
        //

        headerLength = pFrame->NotContiguous.cbLanHeader
                     + pFrame->NotContiguous.cbDlcHeader;
    } else {

        IF_DEBUG(DLC_BUFFERS) {
            DPUT("Buffer is CONTIGUOUS\n");
        }

        userOffset = (WORD)&(((PLLC_DOS_BUFFER)0)->Contiguous.uchAdapterNumber)
                   + sizeof(((PLLC_DOS_BUFFER)0)->Contiguous.uchAdapterNumber);

        //
        // sanity check
        //

        ASSERT(userOffset == 20);

        //
        // no header info in contiguous buffer
        //

        headerLength = 0;
    }

    //
    // if the CF_BREAK flag is set in the Flags parameter then the DOS app
    // requested that the first buffer (presumed NotContiguous) be broken into
    // one buffer containing just the header information and another containing
    // the data. In this case copy no more data to the first buffer
    //

    if (!(Flags & CF_BREAK)) {

        //
        // pDosData points at USER_SPACE - offset 58 for NotContiguous buffer,
        // offset 20 for Contiguous buffer. Bump it by USER_LENGTH (still don't
        // know if we ever expect anything meaningful to be placed at USER_SPACE
        // before we give the buffer to DOS
        //

        pDosData += UserLength;

        //
        // get in dataSpace the amount of space left in the DOS buffer where
        // we are able to copy data. Assume that UserLength doesn't make this
        // go negative (ie LARGE)
        //

        ASSERT(dataSpace >= UserLength);

        dataSpace -= UserLength;
    } else {

        IF_DEBUG(DLC_BUFFERS) {
            DPUT("Buffer has BREAK\n");
        }

        //
        // the DOS app requested BREAK. Set the count of data in this buffer
        // to 0. Use WRITE_WORD since it may be unaligned. Update the other
        // header fields we can't just copy from the NT buffer
        //

        WRITE_WORD(&pDosBuffer->NotContiguous.cbBuffer, 0);
        WRITE_WORD(&pDosBuffer->NotContiguous.offUserData, userOffset + bufferOffset);
        WRITE_WORD(&pDosBuffer->NotContiguous.cbUserData, UserLength);

        //
        // get the next DOS buffer in the list. There may not be one? (Don't
        // expect such a situation)
        //

        bufferOffset = READ_WORD(&pDosBuffer->pNext);
        pDosBuffer = DOS_PTR_TO_FLAT(pDosBuffer->pNext);
        if (pDosBuffer) {
            userOffset = (WORD)&(((PLLC_DOS_BUFFER)0)->Next.cbUserData)
                       + sizeof(((PLLC_DOS_BUFFER)0)->Next.cbUserData);

            //
            // sanity check
            //

            ASSERT(userOffset == 12);
            dataSpace = BufferSize - (BUFFER_2_SIZE + UserLength);
            pDosData = (PBYTE)pDosBuffer + BUFFER_2_SIZE + UserLength;
        } else {

            //
            // that was the last buffer. Either there was just header data or
            // this is a partial copy and therefore not an I-Frame
            //

            IF_DEBUG(DLC_BUFFERS) {
                DPUT("CopyFrame: returning early\n");
            }

            return (Flags & CF_PARTIAL)
                ? LLC_STATUS_LOST_DATA_INADEQUATE_SPACE
                : LLC_STATUS_SUCCESS;
        }
    }

    //
    // frameLength is length of entire frame - must appear in Buffer 1 and
    // Buffer 2s
    //

    frameLength = pFrame->Contiguous.cbFrame;

    //
    // dataLeft is the amount of data left to copy from the frame after the
    // headers have been taken care of. For a Contiguous buffer, this is the
    // same as the length of the frame; for a NotContiguous buffer, this is
    // the length of the frame - the combined length of the LAN and DLC
    // headers
    //

    dataLeft = frameLength - headerLength;

    //
    // get pointer to data in NT buffer and the amount of data to copy (from
    // rest of NT frame)
    //

    pNtData = (PBYTE)pFrame
            + pFrame->Contiguous.offUserData
            + pFrame->Contiguous.cbUserData;

    bufferLeft = pFrame->Contiguous.cbBuffer;

    //
    // dataCopied is amount of data copied to current buffer (1 or 2) and goes
    // in cbBuffer field (aka LENGTH_IN_BUFFER)
    //

    dataCopied = 0;

    //
    // we have copied all the data we could to the first buffer. While there
    // are more DOS buffers, copy data from NT buffer
    //

    do {

        //
        // calculate the amount of space available in the current buffer
        //

        if (dataSpace >= bufferLeft) {
            dataToCopy = bufferLeft;
            dataLeft -= dataToCopy;
            dataSpace -= dataToCopy;
            bufferLeft = 0;
        } else {
            dataToCopy = dataSpace;
            bufferLeft -= dataSpace;
            dataLeft -= dataSpace;
            dataSpace = 0;
        }

        //
        // copy the data. This will fill up the current DOS buffer, exhaust the
        // current NT buffer, or both
        //

        if (dataToCopy) {

            IF_DEBUG(DLC_RX_DATA) {
                DPUT3("CopyFrame: Copying %d bytes from %x to %x\n", dataToCopy, pNtData, pDosData);
            }

            RtlCopyMemory(pDosData, pNtData, dataToCopy);

            //
            // dataCopied accumulates until we fill a DOS buffer
            //

            dataCopied += dataToCopy;

            //
            // update to- and from- pointers for next go round loop
            //

            pDosData += dataToCopy;
            pNtData += dataToCopy;
        }

        //
        // we have run out of space in a DOS buffer, or out of data to copy from
        // the NT buffer
        //

        if (dataLeft) {

            //
            // we think there is data left to copy
            //

            if (!bufferLeft) {

                //
                // finished current NT buffer. Get next one
                //

                pFrame = pFrame->pNext;
                if (pFrame) {
                    bufferLeft = pFrame->Next.cbBuffer;
                    pNtData = (PBYTE)pFrame
                            + pFrame->Contiguous.offUserData
                            + pFrame->Contiguous.cbUserData;

                    IF_DEBUG(DLC_RX_DATA) {
                        DPUT3("CopyFrame: new NT buffer @%x pNtData @%x bufferLeft=%d\n",
                                pFrame, pNtData, bufferLeft);
                    }

                } else {

                    //
                    // no more NT buffers. Is this a partial copy?
                    //

                    DPUT("*** ERROR: dataLeft && no more NT buffers ***\n");
                    ASSERT(Flags & CF_PARTIAL);
                    break;
                }
            }
            if (!dataSpace) {

                //
                // update the current DOS buffer header (it doesn't matter that
                // we use Contiguous, NotContiguous, or Next: these fields are
                // present in all 3 buffer types)
                //

                WRITE_WORD(&pDosBuffer->Contiguous.cbFrame, frameLength);
                WRITE_WORD(&pDosBuffer->Contiguous.cbBuffer, dataCopied);
                WRITE_WORD(&pDosBuffer->Contiguous.offUserData, userOffset + bufferOffset);
                WRITE_WORD(&pDosBuffer->Contiguous.cbUserData, UserLength);

                //
                // and get the next one
                //

                bufferOffset = READ_WORD(&pDosBuffer->pNext);
                pDosBuffer = DOS_PTR_TO_FLAT(pDosBuffer->pNext);

                //
                // if we have another DOS buffer, then it is a Next buffer. Get the
                // buffer variables
                //

                if (pDosBuffer) {
                    pDosData = (PBYTE)pDosBuffer + BUFFER_2_SIZE + UserLength;
                    userOffset = (WORD)&(((PLLC_DOS_BUFFER)0)->Next.cbUserData)
                               + sizeof(((PLLC_DOS_BUFFER)0)->Next.cbUserData);

                    //
                    // sanity check
                    //

                    ASSERT(userOffset == 12);

                    //
                    // get new available space (constant)
                    //

                    dataSpace = BufferSize - (BUFFER_2_SIZE + UserLength);
                    dataCopied = 0;

                    IF_DEBUG(DLC_RX_DATA) {
                        DPUT3("CopyFrame: new DOS buffer @%x pDosData @%x dataSpace=%d\n",
                                pDosBuffer, pDosData, dataSpace);
                    }

                } else {

                    //
                    // no more DOS buffers. Is this a partial copy?
                    //

                    DPUT("*** ERROR: dataLeft && no more DOS buffers ***\n");
                    ASSERT(Flags & CF_PARTIAL);
                    break;
                }
            }
        } else {

            //
            // update the current DOS buffer header
            //

            WRITE_WORD(&pDosBuffer->Contiguous.cbFrame, frameLength);
            WRITE_WORD(&pDosBuffer->Contiguous.cbBuffer, dataCopied);
            WRITE_WORD(&pDosBuffer->Contiguous.offUserData, userOffset + bufferOffset);
            WRITE_WORD(&pDosBuffer->Contiguous.cbUserData, UserLength);
        }

    } while ( dataLeft );

    //
    // if CF_PARTIAL set then we knew we were copying a partial frame before
    // we got here
    //

    return (Flags & CF_PARTIAL)
        ? LLC_STATUS_LOST_DATA_INADEQUATE_SPACE
        : LLC_STATUS_SUCCESS;
}


BOOLEAN
AllBuffersInPool(
    IN DWORD PoolIndex
    )

/*++

Routine Description:

    Returns TRUE if all buffers that a pool has held are currently in the pool.

    Once a buffer has been added to a pool, it cannot be removed, saved by not
    returning it to the pool. Hence this function will always return TRUE if
    the app is well-behaved and all buffers that have been placed in the pool
    are currently in the pool

Arguments:

    PoolIndex   - pool id

Return Value:

    BOOLEAN
        TRUE    - all buffers back
        FALSE   - buffer pool currently contains less than full number of buffers

--*/

{
    BOOLEAN result;
    PDOS_DLC_BUFFER_POOL pBufferPool = &aBufferPools[PoolIndex];

    EnterCriticalSection(&BufferSemaphore);
    result = (pBufferPool->BufferCount == pBufferPool->MaximumBufferCount);
    LeaveCriticalSection(&BufferSemaphore);
    return result;
}
