/*++
*******************************************************************************
* 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:

   xwan.c

Abstract:

   This program is the DSTREAM Miniport Interface Driver to the Sync/570
   Miniport Driver.

   XWAN is a clonable device. An open of "XWAN" returns an unused device node.
   XWAN also is an upper multiplexor. It support multiple devices and device
   types. A LINK_MINIPORT ioctl must be performed to associate a Stream with a
   miniport device. Streams that are not linked only handle "routed" messages
   (e. g. stats!). "Routed" messages use the SNID to indicate the target device.

	here is how multiplexing works

   	stream    stream
 	("linked") ("unlinked")

   	wq  rq    wq  rq
   	|    ^    |    ^
   	|   /|    |   /
   	|  / |    |  /
   	v /  |    v /
  	wput rsrv wput (routed messages only)
   	|^   ^    |^
   	||   |    ||
  --++---+----++-- DSMP API
   	||   |    ||
   	v|   |    v|

  miniport device
    ("linked")



Author:

   Ron Gredvig

--*/

#include "types.h"
#include "stream.h"
#include "queue.h"
#include "sohalloc.h"
#include "ll_control.h"
#include "wan_proto.h"
#include "wan_control.h"
#include "system.h"
#include "strlog.h"
#include "errno.h"
#include "xwan.h"
#include "commonpt.h"

#include "dsmp.h"
#include "DigiLib.h"

#define XWAN_ID 0x310
 
struct module_info xwanrinfo =
{
	XWAN_ID, "XWAN R", INFPSZ, 0x2000, 0x800
};

struct module_info xwanwinfo =
{
	XWAN_ID, "XWAN W", INFPSZ, 0x2000, 0x800
};

struct qinit    xwanrinit =
{
	0, XwanReadService, XwanOpen, XwanClose, 0, &xwanrinfo, 0
};

struct qinit    xwanwinit =
{
	XwanWritePut, XwanWriteService, 0, 0, 0, &xwanwinfo, 0
};

struct streamtab xwaninfo =
{
	&xwanrinit, &xwanwinit, 0, 0
};


extern mblk_t *copymsg();


// NOTE* - The following globals are not protected by spinlocks,
//         since they are accessed  by the streams engine (a single
//         thread).

// Opened Stream Device List
CHAR            gXwanDeviceInitialized = FALSE;
CHAR            gXwanDeviceTable[DEVICE_MAX];
LIST_ENTRY      gXwanDeviceControlListHead;



VOID 
XwanInit(  )
/*++

Routine Description:

This routine is called from STREAMS to initialize the driver.

Arguments:

None.

Return Value:

None.

Comments:

None.

--*/
{
	InitializeListHead( &gXwanDeviceControlListHead );
	gXwanDeviceInitialized = TRUE;
}


static
INT 
XwanOpen( IN queue_t * pQ,
		  IN dev_t Dev,
		  IN INT Flags,
		  IN INT Sflags )
/*++

Routine Description:

	This routine is called by STREAMS for every user request to open this
	Stream device.

Arguments:

	q - A pointer to the read queue
	dev -  The major/minor device number.
	flags - The file flag as defined by sys/file.h
	sflags - The Stream open flag. For this driver 0 (normal open) or CLONEOPEN.

Return Value:

	The device number for success; otherwise an error number.

Comments:

	We are always a clone device.

	If sflag is set to CLONEOPEN:
		find an unused device number
		allocate and initialize device contol structure
		store device contol structure pointer in q->q_ptr queue pair
		mark device table as used
		enable read service routines
		return device number.

	A WAN_SID ioctl will associate the streams device with the miniport hardware

--*/
{

	PXWAN_DEVICE_CONTROL pDevCtl;

	if ( !gXwanDeviceInitialized )
	{
		XwanInit(  );
	}

	//
	// for clone open find unused device
	//
	if ( Sflags == CLONEOPEN )
	{
		for ( Dev = 0; ( Dev <= DEVICE_MAX ) && ( gXwanDeviceTable[Dev] ); Dev++ )
		{
			if ( Dev == DEVICE_MAX )
			{
				return ( OPENFAIL );
			}
		}
	}
	else
	{
		return ( OPENFAIL );
	}

	// allocate device structure

	pDevCtl = DigiAllocMem( NonPagedPool, sizeof( XWAN_DEVICE_CONTROL ) );

	if ( !pDevCtl )
	{
		return ( OPENFAIL );
	}

	RtlZeroMemory ( pDevCtl, sizeof( XWAN_DEVICE_CONTROL ) );
	// initialize device structure

	pDevCtl->pReadQ			= pQ;
	pDevCtl->DeviceNumber	= (short) Dev;
	pDevCtl->LineStatus		= WAN_LINESTATUS_AVAILABLE;

	// save a back pointer to the device control structure in each queue

	pQ->q_other->q_ptr = pQ->q_ptr = ( PCHAR ) pDevCtl;

	// Mark device table

	gXwanDeviceTable[Dev] = 1;

	// Add device to list

	InsertTailList( &gXwanDeviceControlListHead, &pDevCtl->ListEntry );

	qenable( pQ );				   // enable the *read* queue - there might be
	// a better way

	return ( Dev );
}


static
INT 
XwanClose( queue_t * pQ,
		   INT Flags )
/*++

Routine Description:

	This routine is called by STREAMS on the last close of this Stream device.

Arguments:

	pQ - A pointer to the read queue
	Flags - The file flag as defined by sys/file.h

Return Value:

	Zero for success; otherwise an error number.

Comments:

	if linked
		close miniport device

	disable service routines
	clear pointer in q->q_ptr queue pair
	mark device table as free
	deallocate device contol structure

--*/
{
	PXWAN_DEVICE_CONTROL pDevCtl;

	pDevCtl = ( PXWAN_DEVICE_CONTROL ) pQ->q_ptr;

	if ( pDevCtl )
	{
		
		// Disassociate this stream from the miniport
		XwanDisconnectFromMiniport( pDevCtl, pDevCtl->pXwanMiniportInstance);

		// stop the service queue(s)
		noenable( pQ );

		// flush
		flushq( pQ, FLUSHALL );
		flushq( RD( pQ ), FLUSHALL );

		// remove the device from the device list
		RemoveEntryList( &pDevCtl->ListEntry );

		// clear the device table
		gXwanDeviceTable[pDevCtl->DeviceNumber] = 0;

		// clear the back pointer
		pQ->q_ptr = pQ->q_other->q_ptr = 0;

		// free the device structure
		DigiFreeMem( pDevCtl );
	}

	return ( 0 );
}


NTSTATUS
XwanPutFrame( IN PXWAN_DEVICE_CONTROL pDevCtl,
			  IN PMSGB pMsgData
	)
/*++

Routine Description:


Arguments:


Return Value:

	???

Comments:


--*/
{
	DSMP_PUTFRAME   PutFrame;
	NTSTATUS        Status = STATUS_UNSUCCESSFUL;

	ASSERT( pMsgData );

	// We must be Linked
	if (pDevCtl) {

		// make contiguous if not

		if ( pullupmsg( pMsgData, -1 ) == 0 )
		{
			// there was a problem

			ASSERT( FALSE );

			// This isn't the best solution, but don't want this message
			// sent here again so return success so message gets freed instead
			// of queued

			return ( STATUS_SUCCESS );
		}

		PutFrame.Context = 0;
		PutFrame.DataLength = (ULONG)(pMsgData->b_wptr - pMsgData->b_rptr);
		PutFrame.pUserDataBuffer = pMsgData->b_rptr;

		Status = XwanMiniportIo(   DSMP_GENERIC_PUTFRAME,
									pDevCtl,
									&PutFrame,
									sizeof( DSMP_PUTFRAME ) );
	}

	return ( Status );
}


INT
XwanWritePut( PQUEUE pQ,
			  PMSGB pMsg )
/*++

Routine Description:

	This is the write side put routine. It is called via putq() from
	the Stream above. Most write queue processing takes place in this
	routine. Data may be queued for the write side service routine if
	the miniport driver cannot take it.

Arguments:

	pMsg - A pointer to the message block
	pQ - A pointer to the queue that owns this message block

Return Value:

	A code representing action to take. ???

Comments:

	None.

--*/
{
	union WAN_primitives *pWanPrimitive;
	struct wan_reg *pWanReg;
	struct wan_sid *pWanSid;
	struct wan_ctl *pWanCtl;
	struct iocblk  *pIocBlk;
	struct wan_tnioc *pWanTune;
	struct ll_hdioc *pWanPlain;
	struct wan_stioc *pWanStats;
	struct wan_mdmioc *pWanModem;
	struct wan_linestatus *pWanLineStatus;
	hdlcstats_t    *pHdlcStats;
	rfc_1317_rs232_t *pRs232;
	rfc_1317_syncport_t *pRs232Sync;
	PMSGB           pIoctlData;
	LONG            IoctlError;
	PXWAN_DEVICE_CONTROL pDevCtl;
	DSMP_CONFIG     Config;
	DSMP_STATS      Stats;
	DSMP_MODEM      Modem;
	PCHAR           pszMiniportName;
	UNICODE_STRING  ucMiniportName;
	ANSI_STRING     ansiMiniportName;
	NTSTATUS        Status;
	BOOLEAN			bMiniportConnected = FALSE;

	// process messages

	switch ( pMsg->b_datap->db_type )
	{

	case M_DATA:

		// no snid for M_DATA messages
		pDevCtl = ( PXWAN_DEVICE_CONTROL ) pQ->q_ptr;

		ASSERT( pDevCtl );

		if ( pDevCtl )	
		{
			switch ( pDevCtl->State )
			{
			case XWAN_DEVICE_STATE_DIRECT:	/* If DIRECT not OK, start
											   QUEUING.. */
				Status = XwanPutFrame( pDevCtl, pMsg );

				if ( Status == STATUS_SUCCESS )
				{

					// Trace it
					if (pDevCtl->traceq)
					{
						TraceMsg(pDevCtl,pMsg,WAN_TRACE_DIRECTION_TRANSMIT);
					}

					freemsg( pMsg );

					break;
				}
				else
				{
					/* Drop through to QUEUING. */
					pDevCtl->State = XWAN_DEVICE_STATE_QUEUING;
				}

			case XWAN_DEVICE_STATE_QUEUING:	/* Keep QUEUING once started */
				putq( pQ, pMsg );

				// this isn't very efficient
				// perhaps I can enable base on an event
				// DevCtl could have read and write events
				// they are passed to the miniport
				// the miniport sets the write event for no flow control
				// the miniport sets the read event for data available
				// a driver routine (via timeout) checks DevCtl list
				// for pending events and enables queues

				qenable( pQ );

				break;

			case XWAN_DEVICE_STATE_OFF:	// toss messages if we are off
			default:
				freemsg( pMsg );

				break;
			}
		}
		else
		{
			freemsg( pMsg );

			break;
		}

		break;

	case M_PROTO:

		pWanPrimitive = ( union WAN_primitives * ) pMsg->b_rptr;

		switch ( pWanPrimitive->wan_type )
		{

		case WAN_DAT:

			// no snid for WAN_DAT messages

			pDevCtl = ( PXWAN_DEVICE_CONTROL ) pQ->q_ptr;

			ASSERT( pDevCtl );

			if ( pDevCtl )
			{

				PMSGB           pMsgData;

				// Strip of the M_PROTO part of the message and convert it into
				// an M_DATA message.
				pMsgData = pMsg->b_cont;

				switch ( pDevCtl->State )
				{
				case XWAN_DEVICE_STATE_DIRECT:	/* If DIRECT not OK, start
												   QUEUING.. */
					Status = XwanPutFrame( pDevCtl, pMsgData );

					if ( Status == STATUS_SUCCESS )
					{

						// Trace it
						if (pDevCtl->traceq)
						{
							TraceMsg(pDevCtl,pMsgData,WAN_TRACE_DIRECTION_TRANSMIT);
						}

						freemsg( pMsg );

						break;
					}
					else
					{
						/* Drop through to QUEUING. */
						pDevCtl->State = XWAN_DEVICE_STATE_QUEUING;
					}

				case XWAN_DEVICE_STATE_QUEUING:	/* Keep QUEUING once started */
					putq( pQ, pMsg );

					// this isn't very efficient
					// perhaps I can enable base on an event
					// DevCtl could have read and write events
					// they are passed to the miniport
					// the miniport sets the write event for no flow control
					// the miniport sets the read event for data available
					// a driver routine (via timeout) checks DevCtl list
					// for pending events and enables queues

					qenable( pQ );

					break;

				// toss messages if we are off
				case XWAN_DEVICE_STATE_OFF:
				default:
					freemsg( pMsg );

					break;
				}
			}
			else
			{
				freemsg( pMsg );

				break;
			}

			break;

		case WAN_REG:			   /* Start up lower layers */

			pWanReg = ( struct wan_reg FAR * ) pWanPrimitive;

			// validate message size

			if ( ( pMsg->b_wptr - pMsg->b_rptr ) != sizeof( struct wan_reg ) )
			{
				ASSERT( FALSE );   // this shouldn't happen
				freemsg( pMsg );   // qreply an M_ERROR when supported

				break;
			}

			// start port

			pDevCtl = XwanFindDevice( pQ, pWanReg->wan_snid );

			ASSERT( pDevCtl );

			if ( pDevCtl )
			{
				if ( pDevCtl->State == XWAN_DEVICE_STATE_OFF )
				{

					// Tell the protocol that the minport driver
					// is ready.  The protocol will then respond
					// with an MCPROTO WAN_CTL (WCONCF) to confirm
					// the connection.
					mblk_t *wmp;
					register struct wan_ctl FAR *wctl;
    
					// Build and Send control message to the WAN
					if ( (wmp = allocb(sizeof(struct wan_ctl),BPRI_HI) ))
					{

						wmp->b_datap->db_type	= M_PCPROTO;
						wctl					= (struct wan_ctl FAR *)wmp->b_wptr;
						RtlZeroMemory ( wctl, sizeof( struct wan_ctl ) );
						wctl->wan_type			= WAN_CTL;
						wctl->wan_command		= WC_CONNECT;
							  						
						wmp->b_wptr += sizeof(struct wan_ctl);

						qreply( pQ, wmp );

					}

				}
			}

			freemsg( pMsg );

			break;

		case WAN_SID:			   // set sub-network ID

			pWanSid = ( struct wan_sid FAR * ) pWanPrimitive;

			// validate message size

			if ( ( pMsg->b_wptr - pMsg->b_rptr ) != sizeof( struct wan_sid ) )
			{
				ASSERT( FALSE );   // this shouldn't happen
				freemsg( pMsg );

				break;
			}

			// assign the sub-network ID

			pDevCtl = ( PXWAN_DEVICE_CONTROL ) pQ->q_ptr;

			ASSERT( pDevCtl );

			// Connect and Link the Miniport device to the Streams
			if ( pDevCtl )
			{
				bMiniportConnected = XwanConnectToMiniport(pDevCtl,pWanSid->wan_snid);
			}

			if (bMiniportConnected) {

				pDevCtl->Snid = pWanSid->wan_snid;

			}
			else
			{
				// QRelpy an error???
			}

			freemsg( pMsg );

			break;

		default:

			// Toss unknow request
			freemsg( pMsg );
			break;

		}

		break;

	case M_PCPROTO:

		pWanPrimitive = ( union WAN_primitives * ) pMsg->b_rptr;

		switch ( pWanPrimitive->wan_type )
		{
		case WAN_CTL:
			pWanCtl = ( struct wan_ctl * ) pWanPrimitive;

			// validate message size

			if ( ( pMsg->b_wptr - pMsg->b_rptr ) != sizeof( struct wan_ctl ) )
			{
				ASSERT( FALSE );   // this shouldn't happen
				freemsg( pMsg );

				break;
			}

			// no snid for WAN_CTL messages

			pDevCtl = ( PXWAN_DEVICE_CONTROL ) pQ->q_ptr;

			ASSERT( pDevCtl );

			switch ( pWanCtl->wan_command )
			{
			case WC_CONNECT:
				{

					// We're connected so let's set DTR
					Modem.Mask = W_DTR;
					Modem.State = 0;

					XwanMiniportIo( ( ULONG )DSMP_GENERIC_SETMODEM,
										pDevCtl,
										&Modem,
										sizeof( DSMP_MODEM ) );

					pWanCtl->wan_command = WC_CONCNF;
					pWanCtl->wan_status = WAN_SUCCESS;
					qreply( pQ, pMsg );
				}
				break;

			case WC_CONCNF:
				// Awaited confirmation to a previous connect request. External
				//   interface now active again. If status indicates refusal call
				//   driver disconnect routine, indicating no reply required.

				if ( pWanCtl->wan_status == WAN_FAIL )
				{

					// We're not connected so let's lower DTR
					Modem.Mask	= 0;
					Modem.State = 0;

					XwanMiniportIo( ( ULONG )DSMP_GENERIC_SETMODEM,
										pDevCtl,
										&Modem,
										sizeof( DSMP_MODEM ) );

				} 
				else
				{
					
					pDevCtl->State = XWAN_DEVICE_STATE_DIRECT;

					// We're connected so let's set DTR
					Modem.Mask = W_DTR;
					Modem.State = 0;

					XwanMiniportIo( ( ULONG )DSMP_GENERIC_SETMODEM,
										pDevCtl,
										&Modem,
										sizeof( DSMP_MODEM ) );

				}

				freemsg( pMsg );

				break;

			case WC_DISC:
				// Request to remove physical line from a data transfer state.
				//   Status will be returned at a later time through confirm
				//   routine 

				pDevCtl->State = XWAN_DEVICE_STATE_OFF;

				pWanCtl->wan_command = WC_DISCCNF;
				pWanCtl->wan_status = WAN_SUCCESS;
				qreply( pQ, pMsg );

				break;

			case WC_DISCCNF:
				// Awaited confirmation to a previous disconnect request.
				// External interface now active again.
				// We never send a WC_DISC, so we should never receive
				// a respone, so Ignore these responses.

				freemsg( pMsg );

				break;

			default:
				// Discard siliently

				freemsg( pMsg );

				break;
			}

			break;

		default:
			freemsg( pMsg );

			break;
		}

		break;

	case M_IOCTL:

		pIocBlk = ( struct iocblk FAR * ) pMsg->b_rptr;
		pIoctlData = pMsg->b_cont;

		IoctlError = EINVAL;	   // default error until proven otherwise

		switch ( pIocBlk->ioc_cmd )
		{
		case W_SETTUNE:
		case W_GETTUNE:

			if ( pIoctlData && pIocBlk->ioc_count == sizeof( struct wan_tnioc ) )
			{
				pWanTune = ( struct wan_tnioc * ) pIoctlData->b_rptr;

				/* sanity check */
				if ( pWanTune->lli_type != LI_WANTUNE )
				{
					ASSERT( FALSE );

					break;
				}

				/* Error if not found */
				IoctlError = ENODEV;

				// find matching device control struct with SNID from message

				pDevCtl = XwanFindDevice( pQ, pWanTune->lli_snid );

				if ( pDevCtl )
				{

					RtlZeroMemory( &Config, sizeof( DSMP_CONFIG ) );

					/* Store tuning values under given subnetwork */
					if ( pIocBlk->ioc_cmd == W_SETTUNE )
					{

						Config.MaxFrameSize		= (unsigned short) pWanTune->wan_tune.WAN_hd.WAN_maxframe;
						Config.BaudRate			= pWanTune->wan_tune.WAN_hd.WAN_baud;
						Config.StationAddress1	= (unsigned char) pWanTune->wan_tune.WAN_hd.WAN_StationAddress1;
						Config.StationAddress2	= (unsigned char) pWanTune->wan_tune.WAN_hd.WAN_StationAddress2;
			
						// Set Configuration Options
						if (!pWanTune->wan_tune.WAN_hd.WAN_hdplx) {
							Config.LinkOptions |= DSMP_LO_RTS_HIGH;
						}
						if (pWanTune->wan_tune.WAN_options & WAN_NRZI)
						{
							Config.LinkOptions |= DSMP_LO_NRZI;

						}
						if (pWanTune->wan_tune.WAN_options & WAN_WAIT_CTS)
						{
							Config.LinkOptions |= DSMP_LO_WAIT_CTS;

						}

						Status = XwanMiniportIo( ( ULONG ) DSMP_GENERIC_SETCONFIG,
													pDevCtl,
													&Config,
													sizeof( DSMP_CONFIG ) );

						if ( Status == STATUS_SUCCESS )
						{
							// store tuning values in device control structure

							pDevCtl->MaxFrameSize	= Config.MaxFrameSize;
							pDevCtl->BaudRate		= Config.BaudRate;
							pDevCtl->Interface		= Config.Interface;
							pDevCtl->LinkOptions	= Config.LinkOptions;
							pDevCtl->LineStatus		= 0;	// Active

							IoctlError = 0;

						}
						else
						{
							IoctlError = EIO;
						}
					}
					else
					{			   /* W_GETTUNE */

						Status = XwanMiniportIo( ( ULONG )DSMP_GENERIC_GETCONFIG,
													pDevCtl,
													&Config,
													sizeof( DSMP_CONFIG ) );

						if ( Status == STATUS_SUCCESS )
						{
							// copy tuning values into buffer

							pWanTune->wan_tune.WAN_hd.WAN_maxframe	= Config.MaxFrameSize;
							pWanTune->wan_tune.WAN_hd.WAN_baud		= Config.BaudRate;
							pWanTune->wan_tune.WAN_hd.WAN_interface = Config.Interface;

							IoctlError = 0;
						}
						else
						{
							IoctlError = EIO;
						}
					}
				}
			}
			else
			{
				IoctlError = ENOMEM;
			}

			break;

		case W_LINESTATUS:

			if ( pIoctlData && pIocBlk->ioc_count == sizeof( struct wan_linestatus ) )
			{
				pWanLineStatus = ( struct wan_linestatus * ) pIoctlData->b_rptr;

				/* Assume no errors */
				IoctlError = 0;

				// find matching device control struct with SNID from message

				pDevCtl = XwanFindDevice( pQ, pWanLineStatus->wan_linestatus_snid );

				if ( pDevCtl  )
				{

					// copy Device values into buffer
					pWanLineStatus->wan_linestatus_maxframe		= pDevCtl->MaxFrameSize;
					pWanLineStatus->wan_linestatus_linestatus		= pDevCtl->LineStatus;
					pWanLineStatus->wan_linestatus_config_baud	= pDevCtl->BaudRate;
					pWanLineStatus->wan_linestatus_linespeed		= pDevCtl->BaudRate;
					pWanLineStatus->wan_linestatus_options			= 0x00;
					pWanLineStatus->wan_linestatus_hdplx			= 0x00;
					if (!(pDevCtl->LinkOptions & DSMP_LO_RTS_HIGH)) {
						pWanLineStatus->wan_linestatus_hdplx	= 0x02;
						pWanLineStatus->wan_linestatus_options |= 0x01;
					}
					if (pDevCtl->LinkOptions & DSMP_LO_NRZI) {
						pWanLineStatus->wan_linestatus_options |= 0x02;
					}

					// Get the Interface
					RtlZeroMemory( &Config, sizeof( DSMP_CONFIG ) );
					Status = XwanMiniportIo( ( ULONG )DSMP_GENERIC_GETCONFIG,
												pDevCtl,
												&Config,
												sizeof( DSMP_CONFIG ) );

					if ( Status == STATUS_SUCCESS )
					{
						// copy tuning values into buffer.
						pDevCtl->Interface = Config.Interface;
						pWanLineStatus->wan_linestatus_cable_id	= pDevCtl->Interface; 

					}
					else
					{
						IoctlError = EIO;
					}

					// Get the Modem Signals
					RtlZeroMemory( &Modem, sizeof( DSMP_MODEM ) );
					Status = XwanMiniportIo( ( ULONG )DSMP_GENERIC_GETMODEM,
												pDevCtl,
												&Modem,
												sizeof( DSMP_MODEM ) );
					if ( Status == STATUS_SUCCESS )
					{
						// store modem settings in buffer
						pWanLineStatus->wan_linestatus_modem = Modem.State;

					}
					else
					{
						IoctlError = EIO;
					}

					// Get the Stats
					RtlZeroMemory( &Stats, sizeof( DSMP_STATS ) );
					Status = XwanMiniportIo( ( ULONG )DSMP_GENERIC_GETSTATS,
												pDevCtl,
												&Stats,
												sizeof( DSMP_STATS ) );
					if ( Status == STATUS_SUCCESS )
					{
						// copy stats into buffer
						pHdlcStats = &pWanLineStatus->wan_linestatus_stats;

						ASSERT( pHdlcStats );

						pHdlcStats->hc_txgood		= Stats.TxFrames;
						pHdlcStats->hc_txgdbytes	= Stats.TxBytes;
						pHdlcStats->hc_txurun		= Stats.TxUnderrun;
						pHdlcStats->hc_rxgood		= Stats.RxFrames;
						pHdlcStats->hc_rxgdbytes	= Stats.RxBytes;
						pHdlcStats->hc_rxorun		= Stats.RxOverrun;
						pHdlcStats->hc_rxcrc		= Stats.RxCrc;
						pHdlcStats->hc_rxnobuf		= Stats.RxNoBuf;
						pHdlcStats->hc_rxnflow		= Stats.RxNoFlow;
						pHdlcStats->hc_rxoflow		= Stats.RxOverflow;
						pHdlcStats->hc_rxabort		= Stats.RxAbort;
						pHdlcStats->hc_txabort		= Stats.TxAbort;

					}
					else
					{
						IoctlError = ENODEV;
					}
				}
				else
				{
					pWanLineStatus->wan_linestatus_linestatus = WAN_LINESTATUS_AVAILABLE;
				}
			}
			else
			{
				IoctlError = ENOMEM;
			}

			break;

			// case W_GETSTATS:
		case L_GETSTATS:		   // this is bad!! keep now for compatability
			if ( pIoctlData && pIocBlk->ioc_count == sizeof( struct wan_stioc ) )
			{
				pWanStats = ( struct wan_stioc * ) pIoctlData->b_rptr;

				ASSERT( pWanStats );

				/* sanity check */
				if ( pWanStats->lli_type != LI_WANSTATS ) //??? fix types!!!
				{
					ASSERT( FALSE );

					break;
				}

				IoctlError = ENODEV;

				// find matching device control struct with SNID from message

				pDevCtl = XwanFindDevice( pQ, pWanStats->lli_snid );

				if ( pDevCtl  )
				{
					RtlZeroMemory( &Stats, sizeof( DSMP_STATS ) );

					Status = XwanMiniportIo( ( ULONG )DSMP_GENERIC_GETSTATS,
												pDevCtl,
												&Stats,
												sizeof( DSMP_STATS ) );

					if ( Status == STATUS_SUCCESS )
					{
						// copy stats into buffer

						pHdlcStats = &pWanStats->hdlc_stats;

						ASSERT( pHdlcStats );

						pHdlcStats->hc_txgood		= Stats.TxFrames;
						pHdlcStats->hc_txgdbytes	= Stats.TxBytes;
						pHdlcStats->hc_txurun		= Stats.TxUnderrun;
						pHdlcStats->hc_rxgood		= Stats.RxFrames;
						pHdlcStats->hc_rxgdbytes	= Stats.RxBytes;
						pHdlcStats->hc_rxorun		= Stats.RxOverrun;
						pHdlcStats->hc_rxcrc		= Stats.RxCrc;
						pHdlcStats->hc_rxnobuf		= Stats.RxNoBuf;
						pHdlcStats->hc_rxnflow		= Stats.RxNoFlow;
						pHdlcStats->hc_rxoflow		= Stats.RxOverflow;
						pHdlcStats->hc_rxabort		= Stats.RxAbort;
						pHdlcStats->hc_txabort		= Stats.TxAbort;

						IoctlError = 0;
					}
					else
					{
						IoctlError = EIO;
					}
				}
			}
			else
			{
				IoctlError = ENOMEM;
			}

			break;


			// case W_ZEROSTATS:
		case L_ZEROSTATS:		   // this is bad!! keep now for compatability
			if ( pIoctlData && pIocBlk->ioc_count == sizeof( struct ll_hdioc ) )
			{
				pWanPlain = ( struct ll_hdioc * ) pIoctlData->b_rptr;

				/* sanity check */
				if ( pWanPlain->lli_type != LI_PLAIN )
				{
					ASSERT( FALSE );

					break;
				}

				IoctlError = ENODEV;

				// find matching device control struct with SNID from
				// message
				// I don't support snid == * for all right now

				pDevCtl = XwanFindDevice( pQ, pWanPlain->lli_snid );

				if ( pDevCtl  )
				{
					Status = XwanMiniportIo( ( ULONG )DSMP_GENERIC_ZEROSTATS,
												pDevCtl,
												NULL,
												0 );

					if ( Status == STATUS_SUCCESS )
					{
						IoctlError = 0;
					}
					else
					{
						IoctlError = EIO;
					}
				}
			}
			else
			{
				IoctlError = ENOMEM;
			}

			break;

		case W_SETMODEM:
		case W_GETMODEM:
			if ( pIoctlData && pIocBlk->ioc_count == sizeof( struct wan_mdmioc ) )
			{
				pWanModem = ( struct wan_mdmioc * ) pIoctlData->b_rptr;

				/* sanity check */// can't do this - no type in
				// wan_mdmioc
				// if ( pWanModem->lli_type != LI_PLAIN )
				// {
				// ASSERT( FALSE );
				// break;
				// }

				IoctlError = ENODEV;

				// find matching device control struct with SNID from
				// message
				// I don't support snid == * for all right now

				pDevCtl = XwanFindDevice( pQ, pWanModem->snid );

				if ( pDevCtl  )
				{
					if ( pIocBlk->ioc_cmd == W_SETMODEM )
					{
						Modem.Mask = (unsigned char) pWanModem->mask;	// assumes masks alike!
						Modem.State = 0;

						Status = XwanMiniportIo( ( ULONG )DSMP_GENERIC_SETMODEM,
													pDevCtl,
													&Modem,
													sizeof( DSMP_MODEM ) );

						if ( Status == STATUS_SUCCESS )
						{
							// store modem settings in buffer

							pWanModem->signals = Modem.State;

							IoctlError = 0;
						}
						else
						{
							IoctlError = EIO;
						}
					}
					else
					{			   /* W_GETMODEM */
						RtlZeroMemory( &Modem, sizeof( DSMP_MODEM ) );

						Status = XwanMiniportIo( ( ULONG )DSMP_GENERIC_GETMODEM,
													pDevCtl,
													&Modem,
													sizeof( DSMP_MODEM ) );

						if ( Status == STATUS_SUCCESS )
						{
							// store modem settings in buffer

							pWanModem->signals = Modem.State;

							IoctlError = 0;
						}
						else
						{
							IoctlError = EIO;
						}
					}
				}
			}
			else
			{
				IoctlError = ENOMEM;
			}

			break;


		case RFC_1317_RS232:
			if ( pIoctlData && pIocBlk->ioc_count == sizeof( rfc_1317_rs232_t ) )
			{
				pRs232 = ( rfc_1317_rs232_t * ) pIoctlData->b_rptr;

				/* sanity check */// can't do this - no type in
				// rfc_1317_rs232
				// if ( pWanModem->lli_type != LI_PLAIN )
				// {
				// ASSERT( FALSE );
				// break;
				// }

				IoctlError = ENODEV;

				// find matching device control struct with SNID from
				// message

				pDevCtl = XwanFindDevice( pQ, pRs232->rs232PortIndex );

				if ( pDevCtl  )
				{
					RtlZeroMemory( &Config, sizeof( DSMP_CONFIG ) );

					Status = XwanMiniportIo( ( ULONG )DSMP_GENERIC_GETCONFIG,
												pDevCtl,
												&Config,
												sizeof( DSMP_CONFIG ) );

					if ( Status == STATUS_SUCCESS )
					{
						// copy out requested data

						switch ( Config.Interface )
						{
						case DSMP_X21:	/* V.11/RS-422 */
							pRs232->rs232PortType = 3;
							break;
						case DSMP_V28:	/* V.24/RS-232 */
							pRs232->rs232PortType = 2;
							break;
						case DSMP_V35:	/* V.35 */
							pRs232->rs232PortType = 5;
							break;
						default:   /* Other */
							pRs232->rs232PortType = 1;
							break;
						}

						pRs232->rs232PortInSigNumber = 0;
						pRs232->rs232PortOutSigNumber = 0;
						pRs232->rs232PortInPortSpeed =
							pRs232->rs232PortOutPortSpeed = Config.BaudRate;

						IoctlError = 0;
					}
					else
					{
						IoctlError = EIO;
					}
				}
			}
			else
			{
				IoctlError = ENOMEM;
			}

			break;

		case RFC_1317_SYNCPORT:
			if ( pIoctlData && pIocBlk->ioc_count == sizeof( rfc_1317_syncport_t ) )
			{
				pRs232Sync = ( rfc_1317_syncport_t * ) pIoctlData->b_rptr;

				/* sanity check */// can't now - no type in
				// rfc_1317_syncport
				// if ( pWanModem->lli_type != LI_PLAIN )
				// {
				// ASSERT( FALSE );
				// break;
				// }

				IoctlError = ENODEV;

				// find matching device control struct with SNID from
				// message

				pDevCtl = XwanFindDevice( pQ, pRs232Sync->rs232SyncPortIndex );

				if ( pDevCtl  )
				{
					RtlZeroMemory( &Config, sizeof( DSMP_CONFIG ) );

					Status = XwanMiniportIo( ( ULONG )DSMP_GENERIC_GETCONFIG,
												pDevCtl,
												&Config,
												sizeof( DSMP_CONFIG ) );

					if ( Status == STATUS_SUCCESS )
					{
						// copy out requested data

						/* 3 -> split (internal Tx, external Rx), 2 -> external */
						pRs232Sync->rs232SyncPortClockSource = ( Config.BaudRate ? 3 : 2 );


						IoctlError = 0;
					}
					else
					{
						IoctlError = EIO;

						break;	   // leave this case with an error
					}

					RtlZeroMemory( &Stats, sizeof( DSMP_STATS ) );

					Status = XwanMiniportIo( ( ULONG )DSMP_GENERIC_GETSTATS,
												pDevCtl,
												&Stats,
												sizeof( DSMP_STATS ) );

					if ( Status == STATUS_SUCCESS )
					{
						// copy stats into buffer

						pRs232Sync->rs232SyncPortFrameCheckErrs = Stats.RxCrc;
						pRs232Sync->rs232SyncPortTransmitUnderrunErrs = Stats.TxUnderrun;
						pRs232Sync->rs232SyncPortReceiveOverrunErrs = Stats.RxOverrun;
						pRs232Sync->rs232SyncPortInterruptedFrames = 0;
						pRs232Sync->rs232SyncPortAbortedFrames = Stats.RxAbort;

						IoctlError = 0;
					}
					else
					{
						IoctlError = EIO;
					}

				}
			}
			else
			{
				IoctlError = ENOMEM;
			}

			break;

		case W_TRACEON:

			if (pIoctlData->b_wptr - pIoctlData->b_rptr != sizeof(wan_trace_config_t))
			{

				wan_trace_config_t *trace_config  = (wan_trace_config_t *) pIoctlData->b_rptr ;

				pDevCtl = XwanFindDevice( pQ, trace_config->wan_trace_snid );

				if ( pDevCtl  )
				{
					pDevCtl->trace_seq_num = 0;
					pDevCtl->trace_last_modem = 0;
					pDevCtl->traceq = RD(pQ) ;
					IoctlError = 0 ;
				}
				else
				{
					IoctlError = ENOENT ;
				}

			}
			else
			{
				IoctlError = ENOMEM;
			}

			break;

		case W_TRACEOFF:

			if (pIoctlData->b_wptr - pIoctlData->b_rptr != sizeof(wan_trace_config_t))
			{

				wan_trace_config_t *trace_config  = (wan_trace_config_t *) pIoctlData->b_rptr ;

				pDevCtl = XwanFindDevice( pQ, trace_config->wan_trace_snid );

				if ( pDevCtl  )
				{
					pDevCtl->traceq = 0 ;
					IoctlError = 0 ;
				}
				else
				{
					IoctlError = ENOENT ;
				}

			}
			else
			{
				IoctlError = ENOMEM;
			}

			break;

		default:

			break;
		}

		// return the ioctl reply

		pMsg->b_datap->db_type = IoctlError ? M_IOCNAK : M_IOCACK;
		pIocBlk->ioc_error = IoctlError;

		qreply( pQ, pMsg );

		break;

	case M_FLUSH:

		if ( *pMsg->b_rptr & FLUSHW )
		{
			flushq( pQ, FLUSHALL );
		}

		// flush anything on the board ???

		if ( *pMsg->b_rptr & FLUSHR )
		{
			flushq( RD( pQ ), FLUSHALL );
			*pMsg->b_rptr &= ~FLUSHW;
			qreply( pQ, pMsg );
		}

		break;

		// handle any other messages ???

	default:
		freemsg( pMsg );

		break;
	}

	return ( 0 );
}




//STATIC
INT 
XwanWriteService( PQUEUE pQ )
/*++

Routine Description:

	This routine is called from STREAMS to provide processing for the
	write-side queue. Frames are sent to linked devices until the queue is
	empty or the device is full.
	
Arguments:

	pQ - A pointer to the queue requiring service

Return Value:

	Zero for success; otherwise an error number.

Comments:



--*/
{
	PXWAN_DEVICE_CONTROL pDevCtl;
	PMSGB           pMsg;
	union WAN_primitives *pWanPrimitive;
	NTSTATUS        Status;

	pDevCtl = ( PXWAN_DEVICE_CONTROL ) pQ->q_ptr;

	ASSERT( pDevCtl );

	while ( pMsg = getq( pQ ) )
	{

		// process messages
		switch ( pMsg->b_datap->db_type )
		{
			// these should M_DATA messages to a linked device
		case M_DATA:

			Status = XwanPutFrame( pDevCtl, pMsg );

			if ( Status == STATUS_SUCCESS )
			{

				// Trace it
				if (pDevCtl->traceq)
				{
					TraceMsg(pDevCtl,pMsg,WAN_TRACE_DIRECTION_TRANSMIT);
				}

				freemsg( pMsg );
			}
			else
			{
				putbq( pQ, pMsg );

				return ( 0 );
			}

			break;

		case M_PROTO:

			pWanPrimitive = ( union WAN_primitives * ) pMsg->b_rptr;

			if ( pDevCtl && ( pWanPrimitive->wan_type == WAN_DAT ) )
			{
				PMSGB           pMsgData;

				// Strip of the M_PROTO part of the message and convert it into
				// an M_DATA message.
				pMsgData = pMsg->b_cont;

				Status = XwanPutFrame( pDevCtl, pMsgData );

				if ( Status == STATUS_SUCCESS )
				{

					// Trace it
					if (pDevCtl->traceq)
					{
						TraceMsg(pDevCtl,pMsgData,WAN_TRACE_DIRECTION_TRANSMIT);
					}
					
					freemsg( pMsg );
				}
				else
				{
					putbq( pQ, pMsg );

					return ( 0 );
				}
			}
			else
			{
				freemsg( pMsg );
			}

			break;

		default:
			freemsg( pMsg );

			break;
		}
	}

	// we've emptied the queue, so change state to direct and turn off
	// service

	pDevCtl->State = XWAN_DEVICE_STATE_DIRECT;
	noenable( pQ );

	return ( 0 );
}

PMSGB
XwanGetFrame( IN PXWAN_DEVICE_CONTROL pDevCtl,
			  IN SHORT FrameSize
	)
/*++

Routine Description:


Arguments:


Return Value:

	???

Comments:


--*/
{
	PMSGB           pMsgData;
	DSMP_GETFRAME   GetFrame;
	NTSTATUS        Status;

	// alloc data  block(  is there an upper limit ? )

	// The Frame Size is 0, until the link is configured.  If it is Zero, then
	// Don't get the frames.

	if (FrameSize) 
	{

		pMsgData = allocb( FrameSize, BPRI_MED );

		if ( !pMsgData )
		{
			ASSERT( FALSE );		   // we don't expect this to fail

			return ( NULL );		   // maybe do smarter error codes later
		}

		// Initialize the message
		pMsgData->b_next = (mblk_t *) 0;

		// send a GetFrame IOCTL (via Irp) to the miniport
		// build irp, call driver, free irp

		GetFrame.Context			= pMsgData;
		GetFrame.DataMaxLength		= FrameSize;
		GetFrame.DataLength			= 0;
		GetFrame.pUserDataBuffer	= pMsgData->b_wptr;

		Status = XwanMiniportIo( ( ULONG ) DSMP_GENERIC_GETFRAME,
									pDevCtl,
									&GetFrame,
									sizeof( DSMP_GETFRAME ) );

		if ( ( Status == STATUS_SUCCESS ) && ( GetFrame.DataLength != 0 ) )
		{

			// we got a frame so alloc control block, init control block,
			// link control and data blocks

			pMsgData->b_wptr += GetFrame.DataLength;

			return ( pMsgData );
		}
		else
		{
			freemsg( pMsgData );

			return ( NULL );
		}
	}
	else
	{
		return (NULL);
	}

}


// STATIC
INT 
XwanReadService( PQUEUE pQ )
/*++

Routine Description:

	This routine is called from STREAMS to process the read-side queue. It
	checks for messages on it’s queue and sends them to the next STREAMS
	module if possible. Then, it checks for messages from the miniport driver
	if it is linked. If the message can’t be sent up it is queued.

Arguments:

	pQ - A pointer to the queue requiring service

Return Value:

	Zero for success; otherwise an error number.

Comments:

	This service routine is *always* enabled to poll for upbound messages.

--*/
{

	PXWAN_DEVICE_CONTROL pDevCtl;
	PMSGB           pMsg;
	USHORT          FrameSize;

	// process queued messages

	for ( ;; )
	{
		pMsg = getq( pQ );		   // get a message

		if ( pMsg )				   // there was one
		{
			if ( canput( pQ->q_next ) )	// there is room above
			{
				putnext( pQ, pMsg );	// send it up
			}
			else
			{					   // there isn't room above
				putbq( pQ, pMsg );   // requeue the message

				break;			   // and break out of loop
			}
		}
		else
		{						   // no more messages so break out of loop
			break;
		}
	}

	pDevCtl = ( PXWAN_DEVICE_CONTROL ) pQ->q_ptr;

	ASSERT( pDevCtl );

	if ( !XWAN_CONNECTED(pDevCtl) )
	{
		qenable( pQ );			   // keep this running, there may be a better
								   // way

		return ( 0 );
	}

	// process queued messages

	FrameSize = (USHORT) pDevCtl->MaxFrameSize;

	for ( ;; )
	{
		if ( canput( pQ ) )		   // there is room here
		{
			pMsg = XwanGetFrame( pDevCtl, FrameSize );	// get a message

			if ( pMsg )			   // there was one
			{

				// Trace it
				if (pDevCtl->traceq)
				{
					TraceMsg(pDevCtl,pMsg,WAN_TRACE_DIRECTION_RECEIVE);
				}

				if ( !pQ->q_first && canput( pQ->q_next ) )	// no messages queued
					// and room above
				{
					putnext( pQ, pMsg );	// send it up
				}
				else
				{				   // there isn't room above
					putq( pQ, pMsg );	// queue the message
					// here
				}
			}
			else
			{					   // no more messages so break out of loop
				break;
			}
		}
		else
		{						   // there is no room
			break;				   // so break out of loop
		}
	}

	qenable( pQ );				   // keep this running, there may be a better
								   // way

	return ( 0 );
}

PXWAN_DEVICE_CONTROL
XwanFindDevice( IN PQUEUE pQ, IN ULONG Snid )
/*++

Description:

	This routine finds a the device control structure for the given Snid.

Parameters:

	pQ - a pointer to the Stream's write q
	Snid - the sub-network ID of interest

Return Value:

	A pointer to the device control structure matching the Snid.
	A NULL pointer if no device control structures matchthe Snid.

--*/
{
	PXWAN_DEVICE_CONTROL pDevCtl;
	PLIST_ENTRY     pListEntry;

	pDevCtl = ( PXWAN_DEVICE_CONTROL ) pQ->q_ptr;	// by convention

	ASSERT( pDevCtl );

	if ( Snid != pDevCtl->Snid )   // the Snid isn't for this stream
	{
		// determine miniport device from list

		pDevCtl = NULL;			   // make sure this is NULL until a match is
		// found

		// walk the list from the head until the flink points back to
		// the head

		for ( pListEntry  = gXwanDeviceControlListHead.Flink;
			  pListEntry != &gXwanDeviceControlListHead;
			  pListEntry  = pListEntry->Flink )
		{
			pDevCtl = CONTAINING_RECORD( pListEntry,
										 XWAN_DEVICE_CONTROL,
										 ListEntry );

			if ( Snid == pDevCtl->Snid )	// found a match!
			{
				break;
			}
			else
			{
				pDevCtl = NULL;	   // make sure this is NULL until a match
				// is found
			}
		}
	}

	return ( pDevCtl );
}


STATIC VOID
TraceMsg( IN PXWAN_DEVICE_CONTROL pDevCtl,
		  IN mblk_t *pMsg,
		  IN int Dir
	)
/*++

Description:

	This routine finds a the device control structure for the given Snid.

Parameters:

	pDevCtl - The Device this Message id Associated with
	pMsg	- Msg to be traced
	Dir		- the sub-network ID of interest

Return Value:

	VOID

--*/
{

	mblk_t *pCopyMsg,*pTraceMsg;

    if ( canput(pDevCtl->traceq->q_next) )
	{

		pCopyMsg = copymsg(pMsg) ; 

		if (pCopyMsg) 
		{

			pTraceMsg = allocb(sizeof(wan_trace_t), BPRI_MED) ;

			if(pTraceMsg)
			{
				DSMP_MODEM	Modem;
				wan_trace_t *pTraceHdr;

				// Get the Modem Status, If this fails we will still trace the message 
				RtlZeroMemory( &Modem, sizeof( DSMP_MODEM ) );
				XwanMiniportIo( ( ULONG ) DSMP_GENERIC_GETMODEM,
									pDevCtl,
									&Modem,
									sizeof( DSMP_MODEM ) );

				pTraceHdr = (wan_trace_t *) pTraceMsg->b_rptr; 
				pTraceMsg->b_cont = pCopyMsg ;

				pTraceHdr->wan_trace_snid        = pDevCtl->Snid ;				// SNID
				pTraceHdr->wan_trace_sequence    = pDevCtl->trace_seq_num++ ;	// Sequence Number
				pTraceHdr->wan_trace_direction   = Dir ;						// Direction	
				pTraceHdr->wan_trace_time        = curtime() ;			        // Time Stamp
				pTraceHdr->wan_trace_modem       = Modem.State;					// Modem Signals
				pTraceHdr->wan_trace_modem_delta = Modem.State ^ pDevCtl->trace_last_modem ; // Modem Signals Delta 
				pTraceHdr->wan_trace_cable_id    = pDevCtl->Interface ; 		// Cable_ID 	
				pTraceHdr->wan_trace_line_speed  = pDevCtl->BaudRate ;			// Line Speed 
				pTraceHdr->wan_trace_line_status = pDevCtl->LineStatus ;		// Line Status

				pTraceMsg->b_wptr = pTraceMsg->b_rptr + sizeof(wan_trace_t) ;

				pDevCtl->trace_last_modem = Modem.State ;
				pTraceMsg->b_datap->db_type = M_PROTO ;

				putnext(pDevCtl->traceq,pTraceMsg ) ; 
			}
			else
			{
				freemsg(pCopyMsg) ;
			}
		}
	}	
}

