/*++
*******************************************************************************
* Copyright (c) 1990-97 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:

   sohdstrm.c

Abstract:

   This module is the DSTREAMS subsystem thread.

Author:

   Ron Gredvig

Revision History:

   $History: sohdstrm.c $
 * 
 * *****************  Version 4  *****************
 * User: Ron          Date: 10/09/97   Time: 9:55a
 * Updated in $/Components/Windows/NT/Sync/Build/DigiSoh/Src
 * Periodic check-in to Prepare for Sandy. Functional with external
 * loopback on 570.
 *
 * *****************  Version 3  *****************
 * User: Ron          Date: 4/11/97    Time: 5:42p
 * Updated in $/Components/Windows/NT/Sync/Build/DigiSoh/Src
 * Weekly check-in.
 *
 * *****************  Version 2  *****************
 * User: Ron          Date: 4/05/97    Time: 5:04p
 * Updated in $/Components/Windows/NT/Sync/Build/DigiSoh/Src
 * Update to work with digiutil library.
 *
 * *****************  Version 1  *****************
 * User: Ron          Date: 3/27/97    Time: 5:04p
 * Created in $/Components/Windows/NT/Sync/Build/DigiSoh/Src
 * Initial check in of DigiSOH (STREAMS On Host) driver components.

--*/

//
// Spider Streams specific include files
//
#include "types.h"
#include "stream.h"
#include "stropts.h"
#include "errno.h"
#include "queue.h"
#include "cnt_arry.h"

//
// DigiSoh specific include files
//
#include "digisoh.h"
#include "dsioctl.h"
#include "sohioctl.h"
#include "sohdstrm.h"
#include "sohshead.h"
#include "sohalloc.h"
#include "wan_proto.h"

#include "digilib.h"

//
// Global constants
//
#define RUNQLIMIT 20

//
// Global variables
//
int             gLinkError;
INT_PTR             cnt_arry[MAX_CNT_ARRY];
struct user     u;

//
// External variables
//
extern int      errno;

//#ifndef _DSSTREAMS_DOT_C
//#define _DSSTREAMS_DOT_C
//static char RCSInfo_DsStreamsDotC[] = "$Header: /Components/Windows/NT/Sync/Build/DigiSoh/Src/sohdstrm.c 4     10/09/97 9:55a Ron $";

//#endif

///////////////////////////////////////////////////////////////////////////////
//
// Internal Support Routine Prototypes
//

VOID            SohStreamsOpen( IN PSOH_STREAM_OBJECT pStreamObj );
VOID            SohStreamsClose( IN PSOH_STREAM_OBJECT pStreamObj );
VOID            SohStreamsPutmsg( IN PSOH_STREAM_OBJECT pStreamObj );
VOID            SohStreamsGetmsg( IN PSOH_STREAM_OBJECT pStreamObj );
VOID            SohStreamsIoctl( IN PSOH_STREAM_OBJECT pStreamObj );
VOID            SohStreamsLink( IN PSOH_STREAM_OBJECT pStreamObj );
VOID            SohStreamsPush( IN PSOH_STREAM_OBJECT pStreamObj );

int             finddev( char *s );
int             findmod( char *s );
int             findentry( struct devtab * tab, char *s );
int             mystrcmp( char *s1, char *s2 );

VOID            SohTickCountTest( IN PLARGE_INTEGER pTicks, IN PKEVENT pEvent, IN PLARGE_INTEGER pTimeout, IN LONG Iterations );


///////////////////////////////////////////////////////////////////////////////
//
// Exported Routines
//

VOID
SohStreams( IN PVOID pStartContext )

/*++

Routine Description:

This routine is the STREAMS Subsystem thread.

Arguments:

pStartContext - a pointer to the Device Object

Return Value:

None.

Comments:

This is a continuous loop. It monitors Stream Objects for IRPs to process.

??? Is there a more effcient way for this loop to run ?

--*/

{
	PSOH_RUNTIME_ENV	pSohRunTimeEnv;
	PSOH_STREAM_OBJECT	pStreamObj;
	PLIST_ENTRY			pListEntry;
	ULONG				Status;
	LARGE_INTEGER		Timeout;
	SHORT				RunQCnt;
	LARGE_INTEGER		Ticks;
	ULONG				Count;
	KIRQL				StrmKirql;

	DigiDump( DIGIFLOW, ( "DigiSoh: Entering SohStreams\n" ) );

	// initialize local variables
	pSohRunTimeEnv = pStartContext;
	Timeout.QuadPart = -100000;	   // 10 milli-second, must be negative

#if DBG

	SohTickCountTest( &Ticks, &pSohRunTimeEnv->evStreams, &Timeout, 5 );

#endif //DBG


	while ( !gSohRuntimeEnv.Unloading )
	{

		// the dispatch routine will set events when there is work to do
		Status = KeWaitForSingleObject( &pSohRunTimeEnv->evStreams,
										Executive,
										KernelMode,
										TRUE,
										&Timeout );

		runtoq(  );

		// walk through the active list once
		for ( Count = 0; Count < pSohRunTimeEnv->StreamObjectCount; Count++ )
		{
			// get a Stream object to process
			pListEntry = ExInterlockedRemoveHeadList(&pSohRunTimeEnv->StreamObjectListHead,
													 &pSohRunTimeEnv->SpinLock );

			if ( pListEntry == 0 )
			{
				continue;		   // nothing on the list
			}


			pStreamObj = CONTAINING_RECORD( pListEntry,
											SOH_STREAM_OBJECT,
											ListEntry );
			 
			// Don't let Cleanup Run for this stream
			KeAcquireSpinLock( &pStreamObj->SpinLock, &StrmKirql );
			if ( pStreamObj->CancelState == SOH_PROCESS_STATE_CANCEL_PENDING)
			{
				// Only Closes with be processed during cleanup.
				// The Cleanup routine will set the CLOSE state.

				KeReleaseSpinLock( &pStreamObj->SpinLock, StrmKirql );

				// return Stream object to list
				ExInterlockedInsertTailList( &pSohRunTimeEnv->StreamObjectListHead,
											 &pStreamObj->ListEntry,
											 &gSohRuntimeEnv.SpinLock );

				continue;

			}
			pStreamObj->ProcessState = SOH_PROCESS_STATE_DSTRM_RUNNING;
			KeReleaseSpinLock( &pStreamObj->SpinLock, StrmKirql );

			switch ( pStreamObj->State )
			{
			case SOH_STREAM_STATE_OPENING:

				// process a Stream open request
				SohStreamsOpen( pStreamObj );

				break;

			case SOH_STREAM_STATE_ACTIVE:

				// process messages on the Stream head read queue
				SohStreamsGetmsg( pStreamObj );

				// put messages on the Stream head write queue
				SohStreamsPutmsg( pStreamObj );

				// put ioctls on the Stream head write queue
				if ( pStreamObj->IoctlState == IoctlQueuedState )
				{
					SohStreamsIoctl( pStreamObj );	// completes synchronously
				}

				// link two Streams together
				if ( pStreamObj->LinkState == LinkQueuedState )
				{
					SohStreamsLink( pStreamObj );	// completes synchronously
				}

				// push a module on a Stream
				if ( pStreamObj->PushState == PushQueuedState )
				{
					SohStreamsPush( pStreamObj );	// completes synchronously
				}

				break;

			case SOH_STREAM_STATE_CLOSING:

				SohStreamsClose( pStreamObj );

				break;
			}

			
			//
			// DEJ  This is bad code in that before we would put the list object on the list and
			// then use the fields in the list object.  Once you put an object on a list you should
			// consider all fields of that object invalid. So I moved  all references to the object above putting it
			// on the list.  Having the event a part of the object is very very bad, the event 
			// be in the device extension.    
			//

			// Free so the Cleanup routine could run for this stream
			KeAcquireSpinLock( &pStreamObj->SpinLock, &StrmKirql );
			pStreamObj->ProcessState = SOH_PROCESS_STATE_IDLE;
			
			KeReleaseSpinLock( &pStreamObj->SpinLock, StrmKirql );
			KeAcquireSpinLock( &pSohRunTimeEnv->SpinLock, &StrmKirql );
			
			if (pStreamObj->CancelState == SOH_PROCESS_STATE_CANCEL_PENDING)
			{
				KeSetEvent( &pStreamObj->evCleanup,
							0,
							FALSE );
			}

			InsertTailList( &pSohRunTimeEnv->StreamObjectListHead, &pStreamObj->ListEntry );	
			KeReleaseSpinLock( &pSohRunTimeEnv->SpinLock, StrmKirql );
			
			
			// run a few things queued on the run queue or run bufcall queue
			RunQCnt = 0;
			while ( runq(  ) || runbcq(  ) )
			{
				if ( RunQCnt++ > RUNQLIMIT )
				{
					break;
				}
			}

		}

		// set event for Irp completer thread
		KeSetEvent( &gSohRuntimeEnv.evCompleter,
					0,
					FALSE );

	}

	DigiDump( DIGIFLOW, ( "DigiSoh: Leaving SohStreams\n" ) );

	PsTerminateSystemThread( STATUS_SUCCESS );

	return;
}

///////////////////////////////////////////////////////////////////////////////
//
// Internal Support Routines
//

VOID
SohStreamsOpen( IN PSOH_STREAM_OBJECT pStreamObj )

/*++

Routine Description:

This routine opens a Stream on the STREAMS Subsystem thread.

Arguments:

pStreamObj - a pointer to the Stream Object

Return Value:

None.

Comments:

None.

--*/

{
	PSTR            pszDevName;
	PDS_OPEN        pDsOpen;
	LONG            StreamNum;
	LONG            DevNum;

	DigiDump( DIGIFLOW, ( "DigiSoh: SohStreamsOpen - IRP=0x%x\n",
					  pStreamObj->pOpenIrp ) );

	// get irp info

	pDsOpen = ( PDS_OPEN ) pStreamObj->pOpenIrp->AssociatedIrp.SystemBuffer;
	pszDevName = pDsOpen->DeviceName;

	DevNum = finddev( pszDevName );

	if ( DevNum == -1 )
	{
		StreamNum = -1;
		errno = ENXIO;
	}
	else
	{
		// set up proc and user structures ???

		StreamNum = shopen( DevNum,
							( major( DevNum ) == CLONEDEV ) ? CLONEOPEN : 0,
							pStreamObj );
	}

	pStreamObj->pOpenIrp->IoStatus.Status = STATUS_SUCCESS;
	pStreamObj->pOpenIrp->IoStatus.Information = sizeof( DS_OPEN );

	if ( StreamNum != -1 )
	{
		pDsOpen->hStream = pStreamObj->hObjectId;
		pDsOpen->ErrorCode = 0;

		pStreamObj->iStream = StreamNum;	// qfd or queue index
		pStreamObj->State = SOH_STREAM_STATE_OPEN;
	}
	else
	{
		pDsOpen->hStream = NULL;
		pDsOpen->ErrorCode = errno;

		pStreamObj->State = SOH_STREAM_STATE_OPENFAILED;
	}

	return;
}								   // end SohStreamsClose

VOID
SohStreamsClose( IN PSOH_STREAM_OBJECT pStreamObj )

/*++

Routine Description:

This routine closes a Stream on the STREAMS Subsystem thread.

Arguments:

pStreamObj - a pointer to the Stream Object

Return Value:

None.

Comments:

None.

--*/

{
	PIRP            pIrp;
	ULONG           Status;

	DigiDump( DIGIFLOW, ( "DigiSoh: SohStreamsClose - IRP=0x%x\n", pStreamObj->pCloseIrp ) );

	if ( pStreamObj->pOpenIrp )
	{

		pStreamObj->pOpenIrp->IoStatus.Status = STATUS_CANCELLED;
		pStreamObj->pOpenIrp->IoStatus.Information = 0;

		// If we haven't finished the open yet, then no other activity
		// has taken place, so we can honor the close without any further
		// processing.
		if ( pStreamObj->StateBeforeTheClose == SOH_STREAM_STATE_OPENING ) 
		{
			pStreamObj->pCloseIrp->IoStatus.Status = STATUS_SUCCESS;
			pStreamObj->iStream = 0;	// qfd or queue index
			pStreamObj->State = SOH_STREAM_STATE_CLOSED;
			return;
		}

	}

	if ( pStreamObj->pIoctlIrp )
	{
		// an ioctl didn't get completed before Stream entered closing state
		// mark it as cancelled, completer thread will complete it

		pStreamObj->IoctlState = IoctlCompleteState;

		pStreamObj->pIoctlIrp->IoStatus.Status = STATUS_CANCELLED;
		pStreamObj->pIoctlIrp->IoStatus.Information = 0;
	}

	if ( pStreamObj->pLinkIrp )
	{
		// a Link didn't get completed before the Stream entered closing state
		// mark it as cancelled, completer thread will complete it

		pStreamObj->LinkState = LinkCompleteState;

		pStreamObj->pLinkIrp->IoStatus.Status = STATUS_CANCELLED;
		pStreamObj->pLinkIrp->IoStatus.Information = 0;
	}

	if ( pStreamObj->pPushIrp )
	{
		// a Push didn't get completed before the Stream entered closing state
		// mark it as cancelled, completer thread will complete it

		pStreamObj->PushState = PushCompleteState;

		pStreamObj->pPushIrp->IoStatus.Status = STATUS_CANCELLED;
		pStreamObj->pPushIrp->IoStatus.Information = 0;
	}

	// pending putmsg's are moved to completed list
	// mark them as cancelled, completer thread will complete them

	while ( pIrp = SohDequeueMsg( &gSohRuntimeEnv.SpinLock,
								  &pStreamObj->PendingPutmsgListHead ) )
	{
		pIrp->IoStatus.Status = STATUS_CANCELLED;
		pIrp->IoStatus.Information = 0;

		// ??? Is a lock really required ??? We hold Stream Object
		ExInterlockedInsertTailList( &pStreamObj->CompletedPutmsgListHead,
									 &pIrp->Tail.Overlay.ListEntry,
									 &gSohRuntimeEnv.SpinLock );
	}

	// pending getmsg's are moved to completed list
	// mark them as cancelled, completer thread will complete them

	while ( pIrp = SohDequeueMsg( &gSohRuntimeEnv.SpinLock,
								  &pStreamObj->PendingGetmsgListHead ) )
	{
		pIrp->IoStatus.Status = STATUS_CANCELLED;
		pIrp->IoStatus.Information = 0;

		// ??? Is a lock really required ??? We hold Stream Object
		ExInterlockedInsertTailList( &pStreamObj->CompletedGetmsgListHead,
									 &pIrp->Tail.Overlay.ListEntry,
									 &gSohRuntimeEnv.SpinLock );
	}

	// close Stream

	if ( pStreamObj->pStreamObjLinkedUnder == NULL )
	{
		// not linked, just close it
		LONG iStream = pStreamObj->iStream;
		Status = shclose( iStream );
	}
	else
	{
		// was linked, don't call shclose (STREAMS bug) just return as closed

		Status = 0;
	}

	if ( Status != -1 )			   // shclose succeeds
	{
		pStreamObj->pCloseIrp->IoStatus.Status = STATUS_SUCCESS;
		pStreamObj->iStream = 0;	// qfd or queue index
		pStreamObj->State = SOH_STREAM_STATE_CLOSED;
	}
	else
	{
		// can we ever really get here ???
		ASSERT( Status != -1 );

		pStreamObj->pCloseIrp->IoStatus.Status = STATUS_INVALID_PARAMETER;
		pStreamObj->State = SOH_STREAM_STATE_CLOSEFAILED;
	}

	return;
}

VOID
SohStreamsGetmsg( IN PSOH_STREAM_OBJECT pStreamObj )

/*++

Routine Description:

This routine gets a message from a Stream on the STREAMS Subsystem thread.

Arguments:

pStreamObj - a pointer to the Stream Object

Return Value:

None.

Comments:

None.

--*/

{
	PMSGB           pMsgB;
	PMSGB           pMsgBControl;
	PMSGB           pMsgBData;
	PDS_IOCTL       pDsIoctl;
	PIOCBLK         pIocB;
	PDS_GETMSG      pDsGetmsg;
	PIRP            pIrp;
	ULONG           ControlLength;
	ULONG           DataLength;
	NTSTATUS		Status = STATUS_SUCCESS;

	// messages on stream head read q

	while ( pMsgB = shget( pStreamObj->iStream ) )
	{
		switch ( pMsgB->b_datap->db_type )
		{
		case M_DATA:
		case M_PROTO:
		case M_PCPROTO:
			// process message if a Getmsg is pending

			pIrp = SohDequeueMsg( &gSohRuntimeEnv.SpinLock,
								  &pStreamObj->PendingGetmsgListHead );

			if ( pIrp )
			{
				DigiDump( DIGIFLOW, ( "DigiSoh: SohStreamsGetmsg getmsg - IRP=0x%x\n", pIrp ) );

				pDsGetmsg = ( PDS_GETMSG ) ( pIrp->AssociatedIrp.SystemBuffer );

				pMsgBControl = pMsgBData = pMsgB;

				// copy control block, if we can

				if ( pMsgB->b_datap->db_type & ( M_PROTO | M_PCPROTO ) )
				{
					ControlLength = (ULONG)(pMsgBControl->b_wptr - pMsgBControl->b_rptr);

					if (ControlLength <= pDsGetmsg->cbMaxControl) {

						RtlCopyBytes( pDsGetmsg->pProviderControl,
									  pMsgBControl->b_rptr,
									  ControlLength );

						pDsGetmsg->cbControl = ControlLength;
					}
					else
					{
						ASSERT( ControlLength <= pDsGetmsg->cbMaxControl );
						Status = STATUS_BUFFER_TOO_SMALL;
					}
					pMsgBData = pMsgBControl->b_cont;
				}
				else
				{
					pDsGetmsg->cbControl = 0;
				}

				// copy any data blocks, if we can

				pDsGetmsg->cbData = 0;

				while ( pMsgBData )
				{

					DataLength = (ULONG)(pMsgBData->b_wptr - pMsgBData->b_rptr);
					if (pDsGetmsg->cbData + DataLength <= pDsGetmsg->cbMaxData) {

						PUCHAR pPutDataTo;
						
						pPutDataTo = (PUCHAR) pDsGetmsg->pProviderData + pDsGetmsg->cbData;
						RtlCopyBytes( pPutDataTo,
									  pMsgBData->b_rptr,
									  DataLength );

						pDsGetmsg->cbData += DataLength;

					}
					else
					{
						ASSERT( 0 );
						Status = STATUS_BUFFER_TOO_SMALL;
						break;
					}
					pMsgBData = pMsgBData->b_cont;
				}

				// free message

				freemsg( pMsgB );

				// move IRP to completed getmsg list

				pIrp->IoStatus.Status = Status;
				pIrp->IoStatus.Information = sizeof( DS_GETMSG );

				ExInterlockedInsertTailList( &pStreamObj->CompletedGetmsgListHead,
											 &pIrp->Tail.Overlay.ListEntry,
											 &gSohRuntimeEnv.SpinLock );

			}
			else
			{
				// return the message to the queue and exit the loop

				shputbq( pStreamObj->iStream, pMsgB );

				return;
			}

			break;

		case M_IOCACK:
		case M_IOCNAK:
			// from ioctl (not link - link handled in headrput)

			DigiDump( DIGIFLOW, ( "DigiSoh: SohStreamsGetmsg ioctl ack/nack - IRP=0x%x\n", pStreamObj->pIoctlIrp ) );

			pDsIoctl = ( PDS_IOCTL ) pStreamObj->pIoctlIrp->AssociatedIrp.SystemBuffer;
			pIocB = ( PIOCBLK ) pMsgB->b_rptr;

			ASSERT( pDsIoctl );
			ASSERT( pIocB );

			// discard unexpected replys
			if ( pDsIoctl->Command != ( ULONG ) pIocB->ioc_cmd )
			{
				freemsg( pMsgB );
				break;
			}

			// update Irp
			pStreamObj->pIoctlIrp->IoStatus.Status = STATUS_SUCCESS;
			pStreamObj->pIoctlIrp->IoStatus.Information = sizeof( DS_IOCTL );

			// set return codes and copy data
			if ( pMsgB->b_datap->db_type == M_IOCACK )
			{
				ASSERT( pIocB->ioc_rval >= 0 );	// rval should be
				// positive

				pDsIoctl->ReturnValue = pIocB->ioc_rval;
				pDsIoctl->ErrorCode = pIocB->ioc_error;

				if ( pMsgB->b_cont )
				{
					ASSERT( pMsgB->b_cont->b_rptr )

						RtlCopyBytes( pDsIoctl->pProviderDataIn,
									  pMsgB->b_cont->b_rptr,
							  pMsgB->b_cont->b_wptr - pMsgB->b_cont->b_rptr );
				}
			}
			else
			{
				pDsIoctl->ReturnValue = -1;

				if ( pIocB->ioc_error )
				{
					pDsIoctl->ErrorCode = pIocB->ioc_error;
				}
				else
				{
					pDsIoctl->ErrorCode = EINVAL;
				}
			}

			// update ioctl state
			pStreamObj->IoctlState = IoctlCompleteState;

			// free message

			freemsg( pMsgB );

			break;

		case M_FLUSH:			   // handled by headrput
		case M_ERROR:			   // don't currently handle this
		case M_PCSIG:			   // don't currently handle this
		default:
			// free message

			freemsg( pMsgB );

			break;
		}
	}

	return;
}

VOID
SohStreamsPutmsg( IN PSOH_STREAM_OBJECT pStreamObj )

/*++

Routine Description:

This routine puts a message to a Stream on the STREAMS Subsystem thread.

Arguments:

pStreamObj - a pointer to the Stream Object

Return Value:

None.

Comments:

None.

--*/

{
	PMSGB           pMsgB;
	PMSGB           pMsgBControl;
	PMSGB           pMsgBData;
	PDS_PUTMSG      pDsPutmsg;
	PIRP            pIrp;
	ULONG           Status;

	// process pending put messages

	if ( pStreamObj->PutmsgBlocked == TRUE )
	{
		return;					   // don't process if blocked
	}

	while ( pIrp = SohDequeueMsg( &gSohRuntimeEnv.SpinLock,
								  &pStreamObj->PendingPutmsgListHead ) )
	{
		DigiDump( DIGIFLOW, ( "DigiSoh: SohStreamsPutmsg - IRP=0x%x\n", pIrp ) );

		pDsPutmsg = ( PDS_PUTMSG ) ( pIrp->AssociatedIrp.SystemBuffer );

		// allocate messages blocks as required
		// and copy IRP data to message block(s)

		if ( pDsPutmsg->cbControl )
		{
			// ??? need a better test than this ???
			ASSERT( pDsPutmsg->cbControl <= SOH_DBSIZE_MAX );
			ASSERT( pDsPutmsg->cbControl > 1 );

			pMsgBControl = allocb( pDsPutmsg->cbControl, BPRI_LO );

			if ( pMsgBControl )
			{
				RtlCopyBytes( pMsgBControl->b_wptr,
							  pDsPutmsg->pProviderControl,
							  pDsPutmsg->cbControl );

				pMsgBControl->b_wptr += pDsPutmsg->cbControl;
			}
			else
			{
				// return irp to pending list and try later

				// ExInterlockedInsertHeadList(
				// &pStreamObj->PendingPutmsgListHead,
				// &pIrp->Tail.Overlay.ListEntry,
				// &pStreamObj->SpinLock );

				SohRequeueMsg( pIrp,
								&gSohRuntimeEnv.SpinLock,
								&pStreamObj->PendingPutmsgListHead,
								SohCancelPutGetMsg );

				break;
			}
		}
		else
		{
			pMsgBControl = 0;
		}

		if ( pDsPutmsg->cbData )
		{
			// ??? need a better test than this ???
			ASSERT( pDsPutmsg->cbData <= SOH_DBSIZE_MAX );

			// ??? this won't chain any msgb's together - ok???
			pMsgBData = allocb( pDsPutmsg->cbData, BPRI_LO );

			if ( pMsgBData )
			{
				RtlCopyBytes( pMsgBData->b_wptr,
							  pDsPutmsg->pProviderData,
							  pDsPutmsg->cbData );

				pMsgBData->b_wptr += pDsPutmsg->cbData;
			}
			else
			{
				// return irp to pending list and try later

				SohRequeueMsg( pIrp,
								&gSohRuntimeEnv.SpinLock,
								&pStreamObj->PendingPutmsgListHead,
								SohCancelPutGetMsg );

				if ( pMsgBControl )
				{
					freemsg( pMsgBControl );
				}

				break;
			}
		}
		else
		{
			pMsgBData = 0;
		}

		ASSERT( pMsgBControl || pMsgBData );	// both are NULL

		if ( pMsgBControl )
		{

			union WAN_primitives *pWanPrimitive;

			// The Control buffer is the first buffer in the message
			pMsgB = pMsgBControl;

			// Is it an M_PROTO or an M_PCPROTO
			pWanPrimitive = ( union WAN_primitives * ) pMsgB->b_rptr;

			if (pWanPrimitive->wan_type == WAN_CTL)
			{
				pMsgB->b_datap->db_type = M_PCPROTO;
			}
			else
			{
				pMsgB->b_datap->db_type = M_PROTO;
			}

			pMsgB->b_cont = pMsgBData;
		}
		else
		{
			pMsgB = pMsgBData;
			pMsgB->b_datap->db_type = M_DATA;
		}

		// put the message on the write q

		Status = shput( pStreamObj->iStream, pMsgB );	// ??? for now

		if ( Status != 1 )
		{
			DigiDump( DIGIVERYLOUD, ( "DigiSoh: SohStreamsPutmsg - blocked\n" ) );

			pStreamObj->PutmsgBlocked = TRUE;	// block until back-enabled
		}

		// move IRP to completed putmsg list

		pIrp->IoStatus.Status = STATUS_SUCCESS;

		ExInterlockedInsertTailList( &pStreamObj->CompletedPutmsgListHead,
									 &pIrp->Tail.Overlay.ListEntry,
									 &gSohRuntimeEnv.SpinLock );

		if ( pStreamObj->PutmsgBlocked == TRUE )
		{
			break;				   // stop processing if blocked
		}
	}

	return;
}

VOID
SohStreamsIoctl( IN PSOH_STREAM_OBJECT pStreamObj )

/*++

Routine Description:

This routine sends an IOCTL to a Stream on the STREAMS Subsystem thread.

Arguments:

pStreamObj - a pointer to the Stream Object

Return Value:

None.

Comments:

None.

--*/

{
	PMSGB           pMsgB;
	PMSGB           pMsgBControl;
	PMSGB           pMsgBData;
	PDS_IOCTL       pDsIoctl;
	PIOCBLK         pIocB;
	ULONG           Status;

	DigiDump( DIGIFLOW, ( "DigiSoh: SohStreamsIoctl\n" ) );

	if ( pStreamObj->IoctlState == IoctlQueuedState )	// these all complete
	{
		// synchronously
		DigiDump( DIGIVERYLOUD, ( "SohStreams: ioctl - IRP=0x%x\n", pStreamObj->pIoctlIrp ) );

		pDsIoctl = ( PDS_IOCTL ) pStreamObj->pIoctlIrp->AssociatedIrp.SystemBuffer;

		// ??? need a better test than this ???
		// assume level above qualified request
		// assume data fits in a single message block
		// ASSERT( pDsIoctl->DataLength != 0 ); // null data ioctls ok!
		ASSERT( pDsIoctl->cbDataIn <= SOH_DBSIZE_MAX );

		// allocate message blocks as required with allocb

		pMsgBControl = allocb( sizeof( struct iocblk ), BPRI_LO );

		if ( pMsgBControl == NULL )
		{
			return;				   // try again later, hopefully alloc will
								   // work then
			// ??? need some kind of limit on how long to try ???
		}

		if ( pDsIoctl->cbDataIn )
		{
			pMsgBData = allocb( pDsIoctl->cbDataIn, BPRI_LO );
			if ( pMsgBData == NULL )
			{
				freemsg( pMsgBControl );
			}
		}
		else
		{
			pMsgBData = NULL;
		}

		// copy Irp control elements to control message block

		RtlZeroMemory( pMsgBControl->b_wptr,
					   sizeof( struct iocblk ) );

		pIocB = ( PIOCBLK ) ( pMsgBControl->b_wptr );
		pIocB->ioc_cmd = pDsIoctl->Command;
		// pIocB->ioc_uid = ?;
		// pIocB->ioc_gid = ?;
		pIocB->ioc_id = (ULONG)(ULONG_PTR)pStreamObj->hObjectId;
		pIocB->ioc_count = pDsIoctl->cbDataIn;

		pMsgBControl->b_wptr += sizeof( struct iocblk );

		// copy Irp data to data message block if there is one

		if ( pMsgBData )
		{
			RtlCopyBytes( pMsgBData->b_wptr,
						  pDsIoctl->pProviderDataIn,
						  pDsIoctl->cbDataIn );

			pMsgBData->b_wptr += pDsIoctl->cbDataIn;
		}

		pMsgB = pMsgBControl;
		pMsgB->b_datap->db_type = M_IOCTL;
		pMsgB->b_cont = pMsgBData;

		pStreamObj->IoctlState = IoctlPendingState;

		Status = shput( pStreamObj->iStream, pMsgB );	// ??? for now

		ASSERT( Status == 1 );

		// Irp gets completed when IOCACK or IOCNACK come back upstream
	}

	return;
}

VOID
SohStreamsLink( IN PSOH_STREAM_OBJECT pStreamObj )

/*++

Routine Description:

This routine links two Streams on the STREAMS Subsystem thread.

Arguments:

pStreamObj - a pointer to the Stream Object

Return Value:

None.

Comments:

None.

--*/

{
	PDS_LINK        pDsLink;
	ULONG_PTR       iLowerStream;
	PSOH_STREAM_OBJECT pStreamObjLower;

	DigiDump( DIGIFLOW, ( "DigiSoh: SohStreamsLink - IRP=0x%x\n",
					    pStreamObj->pLinkIrp ) );

	pDsLink = ( PDS_LINK ) pStreamObj->pLinkIrp->AssociatedIrp.SystemBuffer;

	iLowerStream = (ULONG_PTR)pDsLink->hStreamLower;
	pStreamObjLower = gapStreamObj[iLowerStream];

	ASSERT( pStreamObjLower );

	pDsLink->MuxId = shlink( pStreamObj->iStream, pStreamObjLower->iStream );

	if ( pDsLink->MuxId > 0 )
	{
		pDsLink->ErrorCode = gLinkError;	// do I need to do this ???

		// mark the linked under member

		pStreamObjLower->pStreamObjLinkedUnder = pStreamObj;
	}
	else
	{
		pDsLink->MuxId = -1;
		pDsLink->ErrorCode = gLinkError;
	}

	pStreamObj->pLinkIrp->IoStatus.Status = STATUS_SUCCESS;
	pStreamObj->pLinkIrp->IoStatus.Information = sizeof( DS_LINK );

	pStreamObj->LinkState = LinkCompleteState;

	return;
}

VOID
SohStreamsPush( IN PSOH_STREAM_OBJECT pStreamObj )

/*++

Routine Description:

This routine pushs a module onto a Stream on the STREAMS Subsystem thread.

Arguments:

pStreamObj - a pointer to the Stream Object

Return Value:

None.

Comments:

None.

--*/

{
	PSTR            pszDevName;
	PDS_PUSH        pDsPush;
	LONG            DevNum;

	DigiDump( DIGIFLOW, ( "DigiSoh: SohStreamsPush - IRP=0x%x\n",
					     pStreamObj->pPushIrp ) );

	pDsPush = ( PDS_PUSH ) pStreamObj->pPushIrp->AssociatedIrp.SystemBuffer;

	// make the push request

	pszDevName = pDsPush->ModuleName;

	DevNum = findmod( pszDevName );

	if ( DevNum == -1 )
	{
		pDsPush->ReturnValue = -1;
		errno = ENXIO;
	}
	else
	{
		pDsPush->ReturnValue = shpush( pStreamObj->iStream, DevNum );
	}

	// update irp info

	pStreamObj->pPushIrp->IoStatus.Status = STATUS_SUCCESS;
	pStreamObj->pPushIrp->IoStatus.Information = sizeof( DS_PUSH );

	if ( pDsPush->ReturnValue == -1 )
	{
		pDsPush->ErrorCode = errno;
	}

	pStreamObj->PushState = PushCompleteState;

	return;
}

/***

flush will need it's own function like link and push

				case I_FLUSH:

					pMsgB = allocb( pDsIoctl->DataLength, BPRI_LO );

					if ( pMsgB )
					{
						RtlCopyBytes( pMsgBData->b_wptr,
									  pDsIoctl->pProviderData,
									  pDsIoctl->DataLength );

						pMsgB->b_wptr += pDsIoctl->DataLength;
					}
					else
					{
						// try later

						break;
					}

					pMsgB->b_datap->db_type = M_FLUSH;

					shput( pStreamObj->iStream, pMsgB );

					pStreamObj->IoctlState == IoctlPendingState;

					// Irp gets completed when M_FLUSH comes back upstream

					break;
***/



int
finddev( char *s )
{
	extern struct devtab devtab[];

	return findentry( devtab, s );
}


int
findmod( char *s )
{
	extern struct devtab modtab[];

	return findentry( modtab, s );
}

int
findentry( struct devtab * tab, char *s )
{
	int             i;

	for ( i = 0; ( tab->name ); i++, tab++ )
	{
		if ( !mystrcmp( s, ( char * ) tab->name ) )
			return tab->dev;
	}
	return -1;
}

int
mystrcmp( char *s1, char *s2 )
{
	while ( *s1 && ( *s1 == *s2 ) )
	{
		s1++;
		s2++;
	}
	return *s2 - *s1;
}

VOID
SohTickCountTest( IN PLARGE_INTEGER pTicks,
				  IN PKEVENT pEvent,
				  IN PLARGE_INTEGER pTimeout,
				  IN LONG Iterations )
{

	LONG            i;

	for ( i = 0; i < Iterations; i++ )
	{

		KeQueryTickCount( pTicks );

		DigiDump( DIGIVERYLOUD, ( "DigiSoh: tick count = %d\n", pTicks->QuadPart ) );

		KeWaitForSingleObject( pEvent,
							   Executive,
							   KernelMode,
							   TRUE,
							   pTimeout );
	}

	KeQueryTickCount( pTicks );

	DigiDump( DIGIVERYLOUD, ( "DigiSoh: tick count = %d\n", pTicks->QuadPart ) );

	return;
}
