/*++
*******************************************************************************
* Copyright (c) 1990-99 Digi International Inc., All Rights Reserved                            *
*                                                                                                                                                                                                               *
* This software contains proprietary and confidential information of Digi               *
* International Inc.  By accepting transfer of this copy, Recipient agrees              *
* to retain this software in confidence, to prevent disclosure to others,               *
* and to make no use of this software other than that for which it was                  *
* delivered.  This is an unpublished copyrighted work of Digi International     *
* Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly     *
* prohibited.                                                                                                                                                                           *
*                                                                                                                                                                                                               *
* Restricted Rights Legend                                                                                                                                              *
*                                                                                                                                                                                                               *
* Use, duplication, or disclosure by the Government is subject to                                       *
* restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in                   *
* Technical Data and Computer Software clause at DFARS 252.227-7031 or                  *
* subparagraphs (c)(1) and (2) of the Commercial Computer Software -                            *
* Restricted Rights at 48 CFR 52.227-19, as applicable.                                                         *
*                                                                                                                                                                                                               *
* Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343                            *
*                                                                                                                                                                                                               *
*******************************************************************************

Module Name:

   dxbevent.c

Abstract:

   This module contains dxb event processing routines.  All handling of the 
        dxb event queue is managed here.

Author:

   Michael Zarns

--*/
#include "digidxb.h"

// Define PARANOID only if you really want to add add extended event queue diagnostics.
//#define PARANOID


STATIC INLINE ULONG dxbQueueWrite( 
        PDXB_DEVICE_EXTENSION pExt,
        PDXB_MESSAGE_QUEUE    pDesc,
        PVOID                 pBuf,
        ULONG                 QOffset,
        ULONG                 cb )
{
    return pExt->Queue.Write( pExt, pDesc, QOffset, pBuf, cb );
}


STATIC INLINE ULONG dxbQueueRead( 
        PDXB_DEVICE_EXTENSION pExt,
        PDXB_MESSAGE_QUEUE    pDesc,
        PVOID                 pBuf,
        ULONG                 QOffset,
        ULONG                 cb )
{
    return pExt->Queue.Read( pExt, pDesc, QOffset, pBuf, cb );
}


STATIC INLINE ULONG
dxbGetEventQIn( 
        PDXB_DEVICE_EXTENSION pExt )
{
    return pExt->Message.GetEventQIn( pExt );
}


STATIC INLINE ULONG
dxbGetEventQOut( 
        PDXB_DEVICE_EXTENSION pExt )
{
    return pExt->Message.GetEventQOut( pExt );
}


STATIC INLINE VOID 
dxbSetEventQOut( 
        PDXB_DEVICE_EXTENSION pExt, 
        ULONG                 QOut )
{
    SRAPRINT2(DIGIQUEUE, SRA_MED_LEVEL, 
              ( "DXB!SetEventQOut: pExt 0x%08x, QOut 0x%08x\n", pExt, QOut) );
    pExt->Message.SetEventQOut( pExt, QOut );
}


STATIC INLINE VOID 
dxbGetEventQOffsets( 
        PDXB_DEVICE_EXTENSION pExt, 
        PULONG                pQIn, 
        PULONG                pQOut )
{
    *pQIn  = dxbGetEventQIn( pExt );
    *pQOut = dxbGetEventQOut( pExt );
}


STATIC INLINE ULONG
dxbGetCmdQIn( 
        PDXB_DEVICE_EXTENSION pExt )
{
    return pExt->Message.GetCmdQIn( pExt );
}

STATIC INLINE ULONG
dxbGetCmdQOut( 
        PDXB_DEVICE_EXTENSION pExt )
{
    return pExt->Message.GetCmdQOut( pExt );
}

STATIC INLINE VOID 
dxbSetCmdQIn( 
        PDXB_DEVICE_EXTENSION pExt, 
        ULONG                 QIn )
{
    SRAPRINT2(DIGIQUEUE, SRA_MED_LEVEL, 
              ( "DXB!SetCmdQIn: pExt 0x%08x, QOut 0x%08x\n", pExt, QIn) );
    pExt->Message.SetCmdQIn( pExt, QIn );
}

STATIC INLINE VOID 
dxbGetCmdQOffsets( 
        PDXB_DEVICE_EXTENSION pExt, 
        PULONG                pQIn, 
        PULONG                pQOut )
{
    *pQIn  = dxbGetCmdQIn( pExt );
    *pQOut = dxbGetCmdQOut( pExt );
}


STATIC ULONG 
dxbQueueWriteBlock(
        IN PDXB_DEVICE_EXTENSION pExt, 
        IN PDXB_MESSAGE_QUEUE    pDesc,
        IN PVOID                 pBuf, 
        IN ULONG                 QOffset,
        IN ULONG                 cbWrite, 
        IN BOOLEAN               fWrap, 
        IN BOOLEAN               fPad )
/*++

Description:

        Presents the dxb queue as a circular FIFO, writing a buffer of data into the 
        queue.  Hides the non-circular characteristics of the real Q -- i.e. buffer
        wrapping and non-contiguous buffers within the Q.

        Assumptions:
        
                CmdQ spinlock must be held.

                Space must be available in the queue, including that needed for padding (if 
                requested) and NOPs (if wrapping is not requested).

Parameters:

        pExt
                Defines the target adapter for this request

        pDesc
                Defines the queue into which to write, i.e. the command queue

        pBuf
                Data buffer to copy into the queue

        QOffset
                Offset in the queue at which to start writing

        cbWrite
                Length of buffer, in bytes

        fWrap
                Is this buffer allowed to be span across page boundaries

        fPad
                Is this buffer required to be padded out to the next 4-byte boundary

Return Value:

        ULONG
                New value of QIn.  Buffer wrap is accounted for.

--*/
{
    PUCHAR pSrc = pBuf;
    ULONG QIn   = QOffset;
    ULONG cb    = cbWrite;

    // Loop copying all data into CmdQ, one page at a time.
    while ( cb != 0 )
    {
        ULONG Offset = QIn % pExt->DxbCmdQ.cbPage;
        ULONG ContigSpace = pDesc->cbPage - Offset;

        if ( cb > ContigSpace )
        {
            if ( fWrap )
            {
                ULONG cbWritten = dxbQueueWrite( pExt, pDesc, pSrc, QIn, ContigSpace );
                QIn += cbWritten;
                ASSERT( QIn % pDesc->cbPage  == 0 );

                pSrc += cbWritten;
                cb   -= cbWritten;
            }
            else
            {
                // Wrapping not allowed.  Insert a Nop and skip to start of next page.
                MSG_NOP Nop = { 0 };
                Nop.type = MSG_NOP_TYPE;
                (void)dxbQueueWrite( pExt, pDesc, &Nop, QIn, sizeof( Nop ) );
                QIn += ContigSpace;
            }
        }
        else
        {
            ULONG cbWritten = dxbQueueWrite( pExt, pDesc, pSrc, QIn, cb );
            QIn += cbWritten;
            cb -= cbWritten;
            ASSERT( cb == 0 );
        }
        QIn = dxbQueueNormalize( pDesc, QIn );
    }

    if ( fPad )
    {
        QIn = (ULONG)dxbAlign32( QIn );
        QIn = dxbQueueNormalize( pDesc, QIn );
    }
    return QIn;
}


STATIC INLINE ULONG
dxbQueueCbData( 
        IN ULONG QIn, 
        IN ULONG QOut,
        IN ULONG QMax )
{
    return QIn - QOut & QMax;
}

STATIC INLINE ULONG
dxbQueueCbSpace( 
        IN ULONG QIn, 
        IN ULONG QOut,
        IN ULONG QSize )
{
    return (QOut - QIn - 1) & (QSize - 1);
}

STATIC INLINE ULONG
dxbQueueMsgSizeMax( 
        IN ULONG   cbHeader, 
        IN ULONG   cbControl, 
        IN ULONG   cbData )
{
    ULONG cb = cbHeader + cbControl + 4;        // add 4 for possible NOP insertion
    cb = (ULONG)dxbAlign32( cb );
    cb = (ULONG)dxbAlign32( cb + cbData );
    return cb;
}


NTSTATUS
dxbQueueWriteCommand(
        IN PDXB_DEVICE_EXTENSION pExt,
        IN PVOID                 pHeader,
        IN ULONG                 cbHeader,
        IN PVOID                 pControl,
        IN ULONG                 cbControl,
        IN PVOID                 pData,
        IN ULONG                 cbData,
        IN BOOLEAN               fWrapHdr,
        IN BOOLEAN               fAlignData )
/*++

Description:

        This routine writes a command into the adapter dxb command buffer, updating the 
        "in" pointer accordingly.

Parameters:

        DeviceExt
                Defines the target adapter for this request.

        pHeader
                The header of the message to be inserted into the command queue.

        cbHeader
                Length of header

        pControl
                The control portion of the message.

        cbControl
                Length of control

        pData
                The data portion of the message.

        cbData
                Length of data

        fWrapHdr
                Is header allowed to wrap across a page boundary?

        fAlignData
                Should data portion be 4-byte aligned when placed in command queue?

Return Value:

        STATUS_SUCCESS if successful.

--*/
{
    NTSTATUS Status;
    ULONG QOut;
    ULONG QIn;

    ASSERT( pExt != NULL );
    ASSERT( pHeader != NULL );
    ASSERT( cbHeader != 0 );

    DigiAcquireSpinLock( &pExt->DxbCmdQ.Lock );
    dxbGetCmdQOffsets( pExt, &QIn, &QOut );

    if ( dxbQueueMsgSizeMax( cbHeader, cbControl, cbData ) <= 
         dxbQueueCbSpace( QIn, QOut, pExt->DxbCmdQ.cbTotal ) )
    {
        SRAPRINT2(DIGITXDATA, SRA_MED_LEVEL, 
                  ( "DXB: Tx Bytes:\n"
                    "Header\n") );
        DigiDumpData( DIGITXDATA, pHeader, cbHeader );
        QIn = dxbQueueWriteBlock( pExt, &pExt->DxbCmdQ, pHeader, QIn, cbHeader, 
                                  fWrapHdr, FALSE );

        SRAPRINT2(DIGITXDATA, SRA_MED_LEVEL, ( "Control\n") );
        DigiDumpData( DIGITXDATA, pControl, cbControl );
        QIn = dxbQueueWriteBlock( pExt, &pExt->DxbCmdQ, pControl, QIn, cbControl, 
                                  TRUE, fAlignData );

        SRAPRINT2(DIGITXDATA, SRA_MED_LEVEL, ( "Data\n") );
        DigiDumpData( DIGITXDATA, pData, cbData );
        QIn = dxbQueueWriteBlock( pExt, &pExt->DxbCmdQ, pData, QIn, cbData, TRUE, TRUE );

        dxbSetCmdQIn( pExt, QIn );
        Status = STATUS_SUCCESS;
    }
    else
    {
        Status = STATUS_DXB_CMD_QUEUE_FULL;
    }

    DigiReleaseSpinLock( &pExt->DxbCmdQ.Lock );

    return Status;
}


ULONG
dxbQueueReadBlock(
        PDXB_DEVICE_EXTENSION pExt,
        PDXB_MESSAGE_QUEUE    pDesc,
        PUCHAR                pBuf, 
        ULONG                 QOffset,
        ULONG                 cbRead,
        BOOLEAN               fWrap,
        BOOLEAN               fPad )
/*++

Description:

        This routine is used for all reads from a dxb queue.  It presents the queue
        as a circular buffer, returning the Q offset at which the next read should start.

        ASSUMPTIONS:

                Queue spinlock must be held

                Data is actually present in the queue.  No verification of QOffset+cbRead is done.

Parameters:

        pExt
                Describes the adapter to read from.

        pDesc
                Defines the queue into which to write, i.e. the command queue

        pBuf
                Output buffer into which bytes are read.

        QOffset
                Offset into dxb queue at which to start reading.

        cbRead
                Count of bytes to read.

        fWrap
                Is the data being read allowed to span across page boundaries

        fPad
                Is the data being read required to be padded out to the next 4 bytes boundary.

Return Value:

        New value for QOut.  Buffer wrap is accounted for.

--*/
{
    ULONG cb     = cbRead;
    PUCHAR pDest = pBuf; 
    ULONG QOut   = QOffset;

    ASSERT( fWrap || cb <= pDesc->cbPage );
    // Loop copying data from Event Q, one page at a time.
    while( cb != 0 )
    {
        ULONG Offset = QOut % pDesc->cbPage;
        if ( Offset + cb > pDesc->cbPage )
        {
            ULONG cbToMove = pDesc->cbPage - Offset;
            if ( fWrap )
            {
                if ( pBuf )
                    (void)dxbQueueRead( pExt, pDesc, pDest, QOut, cbToMove );
                pDest += cbToMove;
                cb    -= cbToMove;
            }
            QOut += cbToMove;
            ASSERT( QOut % pDesc->cbPage == 0 );
        }
        else
        {
            if ( pBuf )
                (void)dxbQueueRead( pExt, pDesc, pDest, QOut, cb );
            QOut += cb;
            cb = 0;
        }
        QOut = dxbQueueNormalize( pDesc, QOut );
    }

    if ( fPad && QOut % 4 )
    {
        QOut = (ULONG)dxbAlign32( QOut );
        QOut = dxbQueueNormalize( pDesc, QOut );
    }

    if ( pBuf && cbRead != 0 )
    {
        SRAPRINT2(DIGIRXDATA, SRA_MED_LEVEL, ( "DXB: Rx Bytes:\n") );
        DigiDumpData( DIGIRXDATA, pBuf, cbRead );
    }

    return QOut;
}


typedef struct POLL_STATS
{
    LARGE_INTEGER StartTime;
    LARGE_INTEGER IntervalTime;
    LARGE_INTEGER InProgressTime;
    ULONG nPackets;
} POLL_STATS, *PPOLL_STATS;


STATIC VOID
dxbEventDpc(
        IN PKDPC Dpc, 
        IN PVOID Context,
        IN PVOID Ignored1,
        IN PVOID Ignored2 )
/*++

Description:

        Dxb event processing routine.  This routine is responsible for pulling message out 
        of the DXB event queue for a single adapter.  One instance of this dpc will be
        started for each adapter.

Parameters:

        Context
                Pointer to the device extension for this adapter.

Return Value:

        None

--*/
{
    PDXB_DEVICE_EXTENSION pExt = Context;
    ULONG QOut;
    ULONG QIn;

    ASSERT( pExt );
    ASSERT( pExt->Type == DXB_TYPE_DEVICE_EXT );

    if ( pExt->bShutdown || pExt->EventDpc.StopDpc )
        goto Quit;

    if ( !dxbDpcSetInProgress( pExt, &pExt->EventDpc ) )
        goto Quit;

    dxbGetEventQOffsets( pExt, &QIn, &QOut );
    ASSERT( QIn == dxbAlign32(QIn) );
    ASSERT( QOut == dxbAlign32(QOut) );

#ifdef PARANOID
    {
        ULONG QOutStart = QOut;
        ULONG cbTotal = dxbQueueCbData( QIn, QOut, pExt->DxbEventQ.cbTotal - 1 );
        ULONG i;
        static ULONG chClear = '*';
#endif

        if ( QIn != QOut )
        {
            SRAPRINT2(DIGIEVENTQ, SRA_MED_LEVEL, 
                      ( "DXB!EventDpc: Starting at QOut 0x%x, QIn 0x%x\n", QOut, QIn) );
            while ( QIn != QOut )
            {
#ifdef PARANOID
                ULONG QOutSave = QOut;
                static UCHAR chFill = 'A';
                ULONG i;
                ULONG NewQIn = dxbGetEventQIn( pExt );
                SRAPRINT2(DIGIEVENTQ, SRA_MED_LEVEL, 
                          ( "DXB!EventDpc:                    NewQIn 0x%x\n", NewQIn ) );
#endif

                QOut = DxbEventProcessor( pExt, QOut );
                ASSERT( QOut == dxbAlign32( QOut ) );

                SRAPRINT2(DIGIEVENTQ, SRA_MED_LEVEL, 
                          ( "DXB!EventDpc:             QOut 0x%x\n", QOut ) );

#ifdef PARANOID
                ASSERT( QOut != QOutSave );
                ASSERT( QOut < pExt->DxbEventQ.cbTotal );
                ASSERT( dxbQueueCbData( QIn, QOut, pExt->DxbEventQ.cbTotal - 1 ) <= cbTotal );
                for ( i = QOutSave; i < QOut; i++ )
                    dxbQueueWrite( pExt, &pExt->DxbEventQ, &chFill, i, 1 );
                chFill = chFill == 'Z' ? 'A' : chFill + 1;
#endif
            }

#ifdef PARANOID
            if ( QOut < QOutStart )
            {
                for ( i = QOutStart; i < pExt->DxbEventQ.cbPage; i++ )
                    dxbQueueWrite( pExt, &pExt->DxbEventQ, &chClear, i, 1 );
                for ( i = 0; i < QOut; i++ )
                    dxbQueueWrite( pExt, &pExt->DxbEventQ, &chClear, i, 1 );
            }
            else
                for ( i = QOutStart; i < QOut; i++ )
                    dxbQueueWrite( pExt, &pExt->DxbEventQ, &chClear, i, 1 );
#endif
            dxbSetEventQOut( pExt, QOut );
        }
#ifdef PARANOID
    }
#endif
        
    pExt->Work.Command.DxbDpc.Dpc.DeferredRoutine( Dpc, Context, Ignored1, Ignored2 );
    pExt->Work.Credit.DxbDpc.Dpc.DeferredRoutine( Dpc, Context, Ignored1, Ignored2 );

    pExt->DpcCount++;

    dxbDpcSetNotInProgress( pExt, &pExt->EventDpc );

 Quit:
    return;
}


VOID dxbQueueInitializeEventDpcTimer( IN PDXB_DEVICE_EXTENSION pExt )
{
    dxbDpcInitializeTimer( pExt, &pExt->EventDpc, dxbEventDpc );
}


VOID
dxbQueueStartEventDpcTimer( 
        PDXB_DEVICE_EXTENSION pExt )
{
    dxbDpcStartTimer( &pExt->EventDpc );
}


VOID
dxbQueueStopEventDpcTimer( 
        PDXB_DEVICE_EXTENSION pExt )
{
    dxbDpcStopTimer( &pExt->EventDpc );
}

STATIC VOID
dxbCmdDpc(
        IN PKDPC Dpc, 
        IN PVOID Context,
        IN PVOID Ignored1,
        IN PVOID Ignored2 )
/*++

Description:

        Dxb delayed command processing routine.  This routine is responsible for moving all 
        delayed and queued command messages down to a single adapter -- identified by the 
        context value.

Parameters:

        Context
                Pointer to the device extension for this adapter.

Return Value:

        None.

--*/
{
    PDXB_DEVICE_EXTENSION const pExt = Context;
    PDXB_STREAM_OBJECT pStream;
    PDXB_DPC pDxbDpc;
    ASSERT( pExt );
    ASSERT( pExt->Type == DXB_TYPE_DEVICE_EXT );

    pDxbDpc = &pExt->Work.Command.DxbDpc;
    if ( !dxbDpcSetInProgress( pExt, pDxbDpc ) )
        goto Done;

    while ( ( pStream = dxbWorkCmdRemoveHead( pExt ) ) != NULL )
    {
        NTSTATUS Status = dxbStreamDelayedDispatch( pStream );
        if ( Status == STATUS_DXB_CMD_QUEUE_FULL )
            break;
    }

    dxbDpcSetNotInProgress( pExt, pDxbDpc );

 Done:
    return;
}


VOID 
dxbQueueInitializeCmdWorkQueue( 
        IN PDXB_DEVICE_EXTENSION pExt )
{
    InitializeListHead( &pExt->Work.Command.Head );
    KeInitializeSpinLock( &pExt->Work.Command.Lock );
    dxbDpcInitializeTimer( pExt, &pExt->Work.Command.DxbDpc, dxbCmdDpc );
}

VOID
dxbQueueStartCmdDpcTimer( 
        PDXB_DEVICE_EXTENSION pExt )
{
    dxbDpcStartTimer( &pExt->Work.Command.DxbDpc );
}

VOID
dxbQueueStopCmdDpcTimer( 
        PDXB_DEVICE_EXTENSION pExt )
{
    dxbDpcStopTimer( &pExt->Work.Command.DxbDpc );
}

STATIC VOID
dxbCreditDpc(
        IN PKDPC Dpc, 
        IN PVOID Context,
        IN PVOID Ignored1,
        IN PVOID Ignored2 )
/*++

Description:

        Dxb read-side credit award routine.  This routine is responsible for creating and 
        sending Credit messages down for all streams on an adapter.

Parameters:

        Context
                Pointer to the device extension for this adapter.

Return Value:

        None.

--*/
{
    PDXB_DEVICE_EXTENSION const pExt = Context;
    PDXB_STREAM_OBJECT pStream;
    PDXB_DPC pDxbDpc;
    ASSERT( pExt );
    ASSERT( pExt->Type == DXB_TYPE_DEVICE_EXT );

    pDxbDpc = &pExt->Work.Credit.DxbDpc;
    if ( !dxbDpcSetInProgress( pExt, pDxbDpc ) )
        goto Done;

    while ( ( pStream = dxbWorkCreditRemoveHead( pExt ) ) != NULL )
    {
        NTSTATUS Status = dxbStreamDispatchCreditAwardMessage( pStream );
        if ( Status == STATUS_DXB_CMD_QUEUE_FULL )
            break;
    }

    dxbDpcSetNotInProgress( pExt, pDxbDpc );

 Done:
    return;
}

VOID 
dxbQueueInitializeCreditWorkQueue( 
        IN PDXB_DEVICE_EXTENSION pExt )
{
    InitializeListHead( &pExt->Work.Credit.Head );
    KeInitializeSpinLock( &pExt->Work.Credit.Lock );
    dxbDpcInitializeTimer( pExt, &pExt->Work.Credit.DxbDpc, dxbCreditDpc );
}

VOID
dxbQueueStartCreditDpcTimer( 
        PDXB_DEVICE_EXTENSION pExt )
{
    dxbDpcStartTimer( &pExt->Work.Credit.DxbDpc );
}

VOID
dxbQueueStopCreditDpcTimer( 
        PDXB_DEVICE_EXTENSION pExt )
{
    dxbDpcStopTimer( &pExt->Work.Credit.DxbDpc );
}

STATIC ULONG
dxbQueueReadSlave( 
        struct DXB_DEVICE_EXTENSION *pExt,
        PDXB_MESSAGE_QUEUE          pDesc,
        ULONG                       QueueOffset,
        PVOID                       pBuf,
        ULONG                       cb )
{
    DxbAdapterRead( pExt, pBuf, pDesc->Mode.Slave.Offset + QueueOffset, cb );
    return cb;
}


STATIC ULONG
dxbQueueWriteSlave( 
        struct DXB_DEVICE_EXTENSION *pExt,
        PDXB_MESSAGE_QUEUE          pDesc,
        ULONG                       QueueOffset,
        PVOID                       pBuf,
        ULONG                       cb )
{
    DxbAdapterWrite( pExt, pBuf, pDesc->Mode.Slave.Offset + QueueOffset, cb );
    return cb;
}


STATIC ULONG
dxbQueueReadBusmasterFlat( 
        struct DXB_DEVICE_EXTENSION *pExt,
        PDXB_MESSAGE_QUEUE          pDesc,
        ULONG                       QueueOffset,
        PVOID                       pBuf,
        ULONG                       cb )
{
    ASSERT(pDesc->Mode.Master.VirtualPages[0] != NULL);
    if (pDesc->Mode.Master.VirtualPages[0])
        RtlCopyMemory( pBuf, (PUCHAR)pDesc->Mode.Master.VirtualPages[0] + QueueOffset, cb );
    else
        cb = 0;
    return cb;
}


STATIC ULONG
dxbQueueWriteBusmasterFlat( 
        struct DXB_DEVICE_EXTENSION *pExt,
        PDXB_MESSAGE_QUEUE          pDesc,
        ULONG                       QueueOffset,
        PVOID                       pBuf,
        ULONG                       cb )
{
    ASSERT(pDesc->Mode.Master.VirtualPages[0] != NULL);
    if (pDesc->Mode.Master.VirtualPages[0])
        RtlCopyMemory( (PUCHAR)pDesc->Mode.Master.VirtualPages[0] + QueueOffset, pBuf, cb );
    else
        cb = 0;
    return cb;
}


STATIC ULONG
dxbQueueReadBusmasterWindowed( 
        struct DXB_DEVICE_EXTENSION *pExt,
        PDXB_MESSAGE_QUEUE          pDesc,
        ULONG                       QueueOffset,
        PVOID                       pBuf,
        ULONG                       cb )
{
    ULONG Page = QueueOffset / pDesc->cbPage;
    ULONG Offset = QueueOffset % pDesc->cbPage;
    ULONG cbLeft = cb;
    PUCHAR pDest = pBuf;

    UNREFERENCED_PARAMETER( pExt );
    while ( cbLeft != 0 )
    {
        ULONG cbCurrent = Offset + cbLeft <= pDesc->cbPage ? cbLeft : pDesc->cbPage - Offset;
        ASSERT( Page < pDesc->Mode.Master.nPages );
        ASSERT( pDesc->Mode.Master.VirtualPages[Page] != NULL );
        if (Page < pDesc->Mode.Master.nPages && pDesc->Mode.Master.VirtualPages[Page])
            RtlCopyMemory( pDest, (PUCHAR)pDesc->Mode.Master.VirtualPages[Page] + Offset, cbCurrent );
        else {
            cb = 0;
            break;
        }
        cbLeft -= cbCurrent;
        pDest  += cbCurrent;
        Offset = 0;
        Page++;
    }
    return cb;
}


STATIC ULONG
dxbQueueWriteBusmasterWindowed( 
        struct DXB_DEVICE_EXTENSION *pExt,
        PDXB_MESSAGE_QUEUE          pDesc,
        ULONG                       QueueOffset,
        PVOID                       pBuf,
        ULONG                       cb )
{
    ULONG Page = QueueOffset / pDesc->cbPage;
    ULONG Offset = QueueOffset % pDesc->cbPage;
    ULONG cbLeft = cb;
    PUCHAR pDest = pBuf;

    while ( cbLeft != 0 )
    {
        ULONG cbCurrent = Offset + cbLeft <= pDesc->cbPage ? cbLeft : pDesc->cbPage - Offset;
        ASSERT( Page < pDesc->Mode.Master.nPages );
        ASSERT( pDesc->Mode.Master.VirtualPages[Page] != NULL );
        if (Page < pDesc->Mode.Master.nPages && pDesc->Mode.Master.VirtualPages[Page])
            RtlCopyMemory( (PUCHAR)pDesc->Mode.Master.VirtualPages[Page] + Offset, pDest, cbCurrent );
        else {
            cb = 0;
            break;
        }

        cbLeft -= cbCurrent;
        pDest  += cbCurrent;
        Offset = 0;
        Page++;
    }
    return cb;
}


VOID 
dxbQueueInitAccessMethods(
        IN PDXB_DEVICE_EXTENSION pExt, 
        IN DXB_BUS_MODE          BusMode )
{
    switch ( BusMode )
    {
    case DXB_BUSMODE_SLAVE:
        pExt->Queue.Read  = dxbQueueReadSlave;
        pExt->Queue.Write = dxbQueueWriteSlave;
        break;

    case DXB_BUSMODE_MASTER_FLAT:
        pExt->Queue.Read  = dxbQueueReadBusmasterFlat;
        pExt->Queue.Write = dxbQueueWriteBusmasterFlat;
        break;

    case DXB_BUSMODE_MASTER_WINDOWED:
        pExt->Queue.Read  = dxbQueueReadBusmasterWindowed;
        pExt->Queue.Write = dxbQueueWriteBusmasterWindowed;
        break;
    }
}
