/*++
*******************************************************************************
* 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 )
{
	DigiDump( DIGIQUEUE, ( "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 )
{
	DigiDump( DIGIQUEUE, ( "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 ) )
	{
		DigiDump( DIGITXDATA, ( "DXB: Tx Bytes:\n"
		                       "Header\n") );
		DigiDumpData( DIGITXDATA, pHeader, cbHeader );
		QIn = dxbQueueWriteBlock( pExt, &pExt->DxbCmdQ, pHeader, QIn, cbHeader, 
		                          fWrapHdr, FALSE );

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

		DigiDump( DIGITXDATA, ( "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 )
	{
		DigiDump( DIGIRXDATA, ( "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) );


	if ( QIn != QOut )
	{
		DigiDump( DIGIEVENTQ, ( "DXB!EventDpc: Starting at QOut 0x%x, QIn 0x%x\n", QOut, QIn) );
		while ( QIn != QOut )
		{
			QOut = DxbEventProcessor( pExt, QOut );
			ASSERT( QOut == dxbAlign32( QOut ) );

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

		dxbSetEventQOut( pExt, QOut );
	}

	pExt->Work.Command.DxbDpc.Dpc.DeferredRoutine( Dpc, Context, Ignored1, Ignored2 );

	// Refund credits:
	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 )
{
	RtlCopyMemory( pBuf, (PUCHAR)pDesc->Mode.Master.VirtualPages[0] + QueueOffset, cb );
	return cb;
}


STATIC ULONG
dxbQueueWriteBusmasterFlat( 
	struct DXB_DEVICE_EXTENSION *pExt,
	PDXB_MESSAGE_QUEUE          pDesc,
	ULONG                       QueueOffset,
	PVOID                       pBuf,
	ULONG                       cb )
{
	RtlCopyMemory( (PUCHAR)pDesc->Mode.Master.VirtualPages[0] + QueueOffset, pBuf, cb );
	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 );
		RtlCopyMemory( pDest, (PUCHAR)pDesc->Mode.Master.VirtualPages[Page] + Offset, cbCurrent );

		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 );
		RtlCopyMemory( (PUCHAR)pDesc->Mode.Master.VirtualPages[Page] + Offset, pDest, cbCurrent );

		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;
	}
}

