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

   stress.c

Abstract:

   This module contains the code specific to this stress test.

Author:

   Michael Zarns

--*/
#include <ntddk.h>
#include "digilib.h"
#include "debug.h"
#include "Stress.h"
#include "DsApi.h"

// Define IWANLOOP to run the iwanloop test instead of slo0.
#define IWANLOOP

///////////////////////////////////////////////////////////////////////////////
//
//  Constants and Defines
//

#ifdef IWANLOOP
#define DEVICENAME	"IWANLOOP"

#include "dxbtypes.h"
#ifndef MAXIOCBSZ
#define MAXIOCBSZ 1024
#endif
#include "iwan_control.h"
#include "iwan_proto.h"
#else
#define DEVICENAME	"SLO0"
#endif

// Count of Streams to use
#define NSTREAMS 2
// Count of PutMsgs per Stream
#define NPUTS 140
// Count of GetMsgs per Stream
#define NGETS 140
// Count of bytes per PutMsg buffer
#define NPUTBYTES 4
// Count of bytes per GetMsg buffer
#define NGETBYTES 4


typedef struct MyMsg
{
	PUCHAR Buffer;
	BOOLEAN Completed;
} MY_MSG, *PMY_MSG;

typedef struct MyStream
{
	// Stream handle
	HANDLE hStream;

	// PutMsg stuff
	struct
	{
		// Array of PutMsg Buffers
		PUCHAR Buffers[NPUTS];
		// Array of events indicating completed requests
		KEVENT Events[NPUTS];
		KSPIN_LOCK Lock;
	} Put;

	// GetMsg stuff
	struct
	{
		// Array of GetMsg Buffers
		PUCHAR Buffers[NGETS];
		// Array of events indicating completed requests
		KEVENT Events[NGETS];
		// Count of completed GetMsg requests
		ULONG nCompleted;
		KSPIN_LOCK Lock;
	}	Get;

} MY_STREAM, *PMY_STREAM;


///////////////////////////////////////////////////////////////////////////////
//
//  Data
//

// Array of structs defining all open streams to FrameRelay
MY_STREAM gStreams[NSTREAMS];

// Sxb Device handle
HANDLE ghDevice;

// Flag to stop test (currently settable only by debugger)
BOOLEAN bStop = FALSE;

// Array of Thread handles
HANDLE ghThreads[NSTREAMS][2];

ULONG gPutsRequested = 0;
ULONG gPutsCompleted = 0;
ULONG gGetsRequested = 0;
ULONG gGetsCompleted = 0;

LARGE_INTEGER gStartTime;

#define VERIFY_CONTROL 'COLE'


VOID
PutMsgComplete(
	IN HANDLE   hOwner,
	IN HANDLE   Context,
	IN PVOID    pControl,
	IN ULONG    ControlLength,
	IN PVOID    pData,
	IN ULONG    DataLength,
	IN NTSTATUS IoStatus )
/*++

Description:

	Completion routine called when a PutMsg request has been completed.

--*/
{
	ULONG iStream = (ULONG)(ULONG_PTR)hOwner;
	ULONG iPut = (ULONG)(ULONG_PTR)Context;

	DigiDump( DIGIFLOW, 
	          ( "Enter PutMsgComplete.  iStream 0x%x, iPut 0x%x\n", iStream, iPut ) );

	gPutsCompleted++;
	// Signal the PutMsgPump thread that this request has completed and may be resent
	KeSetEvent( &gStreams[iStream].Put.Events[iPut], 0, FALSE );

	DigiDump( DIGIFLOW, ("Leave PutMsgComplete\n") );
}


VOID
PutMsgPump( IN PVOID Context )
/*++

Description:

	Sends PutMsgs down.

--*/
{
	ULONG iStream = (ULONG)(ULONG_PTR)Context;
	NTSTATUS Status;
	ULONG i;
	KEVENT Event;
	KeInitializeEvent( &Event, SynchronizationEvent, FALSE );
	DigiDump( DIGIFLOW, ("Enter PutMsgPump iStream 0x%x\n", iStream) );

	while ( 1 )
	{
		for ( i = 0; i < NPUTS; i++ )
		{
			LARGE_INTEGER Timeout;
#ifndef IWANLOOP
			ULONG Junk = VERIFY_CONTROL;
#endif
			Timeout.QuadPart = 10 * -10000; // 10 ms

			// Insert delay
			// KeWaitForSingleObject( &Event, Executive, KernelMode, FALSE, &Timeout );

			Status = STATUS_TIMEOUT;
			while ( Status == STATUS_TIMEOUT )
			{
				Status = KeWaitForSingleObject( &gStreams[iStream].Put.Events[i],
				                                Executive, KernelMode, FALSE, &Timeout );
				if ( bStop )
					goto PutMsgPumpExit;
			}

			// Resend the completed PutMsg
			Status = DsPutMsgEx( gStreams[iStream].hStream,
#ifdef IWANLOOP
			                     NULL, 0,
#else
			                     (PVOID)&Junk, sizeof( Junk ),
#endif
			                     gStreams[iStream].Put.Buffers[i], NPUTBYTES,
			                     (HANDLE)i,
										DS_PRIORITY_LOW,
			                     PutMsgComplete );
			gPutsRequested++;
			if ( !NT_SUCCESS( Status ) )
			{
				DigiDump( DIGIERRORS, ("*** DsPutMsg returned 0x%x! ***\n", Status ) );
				// Set the event to allow this request to be retried next time
				KeSetEvent( &gStreams[iStream].Put.Events[i], 0, FALSE );
				continue;
			}
		}
	}

PutMsgPumpExit:;
	DsPurge( gStreams[iStream].hStream, DS_PURGE_PUTMSG );
	DigiDump( DIGIFLOW, ("Leave PutMsgPump\n") );
	PsTerminateSystemThread( Status );
}


VOID
GetMsgComplete(
	IN HANDLE   hOwner,
	IN HANDLE   Context,
	IN PVOID    pControl,
	IN ULONG    ControlLength,
	IN PVOID    pData,
	IN ULONG    DataLength,
	IN NTSTATUS IoStatus )
/*++

Description:

	Completion routine called when a GetMsg request has been completed.

--*/
{
	ULONG iStream = (ULONG)(ULONG_PTR)hOwner;
	ULONG iGet = (ULONG)(ULONG_PTR)Context;
#ifndef IWANLOOP
	PULONG pVerifyControl = pControl;
#endif

	DigiDump( DIGIFLOW, 
	          ( "Enter GetMsgComplete.  iStream 0x%x, iGet 0x%x\n", iStream, iGet ) );

	// Sanity Checking

	gGetsCompleted++;
	if ( IoStatus != STATUS_CANCELLED )
	{
#ifndef IWANLOOP
		if ( pVerifyControl != NULL )
		{
			ASSERT( *pVerifyControl == VERIFY_CONTROL );
		}
#endif

#define  MOD_VAL 1024
		if ( gGetsCompleted % MOD_VAL == 0 )
		{
			LARGE_INTEGER CurrentTime;
			LARGE_INTEGER Interval;
			LARGE_INTEGER Packets;
			LARGE_INTEGER PPS;
			KeQuerySystemTime( &CurrentTime );
			Interval.QuadPart = CurrentTime.QuadPart - gStartTime.QuadPart;
			Packets.QuadPart = gGetsCompleted;
			PPS.QuadPart = Interval.QuadPart == 0 ? 0 : ( Packets.QuadPart * 10000000 ) / Interval.QuadPart;
			DigiDump( DIGITHROUGHPUT, ("Packets received %d, PPS %d\n", gGetsCompleted, PPS.LowPart ) );
		}

		// Signal that this request has completed and may be resent by GetMsgPump thread
		KeSetEvent( &gStreams[iStream].Get.Events[iGet], 0, FALSE );
	}
	else
	{
		ASSERT( bStop );
	}

	DigiDump( DIGIFLOW, ("Leave GetMsgComplete\n") );
}


VOID
GetMsgPump( IN PVOID Context )
/*++

Description:

	Sends GetMsgs down.

--*/
{
	ULONG iStream = (ULONG)(ULONG_PTR)Context;
	NTSTATUS Status = STATUS_SUCCESS;
	ULONG i;

	DigiDump( DIGIFLOW, ("Enter GetMsgPump iStream 0x%x\n", iStream) );

	KeQuerySystemTime( &gStartTime );
	while ( 1 )
	{
		for ( i = 0; i < NGETS; i++ )
		{
			NTSTATUS Status;
			LARGE_INTEGER Timeout;

			Timeout.QuadPart = 100 * -10000; // 0.1 second
			Status = STATUS_TIMEOUT;
			while ( Status == STATUS_TIMEOUT )
			{
				Status = KeWaitForSingleObject( &gStreams[iStream].Get.Events[i],
				                                Executive, KernelMode, FALSE, &Timeout );
				if ( bStop )
					goto GetMsgPumpExit;
			}

			// Resend the completed GetMsg
			Status = DsGetMsgEx( gStreams[iStream].hStream,
#ifdef IWANLOOP
			                     0,
#else
			                     sizeof( ULONG ),
#endif
			                     gStreams[iStream].Get.Buffers[i],
			                     NGETBYTES,
			                     (HANDLE)i,
										DS_PRIORITY_LOW,
			                     GetMsgComplete );
			gGetsRequested++;
			if ( !NT_SUCCESS( Status ) )
			{
				DigiDump( DIGIERRORS, 
					("*** DsGetMsg returned 0x%x, aborting! ***\n", Status ) );
				// Set the event to allow this request to be retried next time
				KeSetEvent( &gStreams[iStream].Get.Events[i], 0, FALSE );
			}
		}
	}

GetMsgPumpExit:;
	DsPurge( gStreams[iStream].hStream, DS_PURGE_GETMSG );
	DigiDump( DIGIFLOW, ("Leave GetMsgPump\n") );
	PsTerminateSystemThread( Status );
}


NTSTATUS
OpenLinks( )
/*++

Description:

	Opens streams to the LOOP device

--*/
{
	PCWSTR SxbDeviceNameString = L"DigiDxb2";
	UNICODE_STRING SxbDeviceName;
	NTSTATUS Status;
	ULONG Error;
	ULONG i;

	DigiDump( DIGIFLOW, ( "Enter OpenLinks\n" ) );

	// Init my data
	RtlZeroMemory( gStreams, sizeof( gStreams ) );

	RtlInitUnicodeString( &SxbDeviceName, SxbDeviceNameString );
	Status = DsOpenDevice( &SxbDeviceName, &ghDevice );
	if ( Status != STATUS_SUCCESS )
	{
		DigiDump( DIGIERRORS, ( "DsOpenDevice failed, Status 0x%x\n", Status ) );
		return( Status);
	}

	for ( i = 0; i < NSTREAMS; i++ )
	{
		// Open a stream to LOOP device
		Status = DsOpen( ghDevice, DEVICENAME, (HANDLE)i, NULL, 0, &gStreams[i].hStream, &Error );
		if ( Status != STATUS_SUCCESS )
		{
			DigiDump( DIGIERRORS, ("DsOpen failed, Status 0x%x, Error 0x%x\n", 
			                      Status, Error ) );
			goto ExitError;
		}
#ifdef IWANLOOP
#define SNID 1
		// Startup the iwanloop device
		{
			HANDLE hStream = gStreams[i].hStream;
			NTSTATUS Status;

			// Send setSnid Ioctl
			{
				ULONG Error;
				iwan_snioc_t SetSnid = {0};
				SetSnid.w_type   = WAN_SNID;
				SetSnid.w_class  = ISDN_BCHAN;
				SetSnid.w_chanid = 1;			// One based?
				SetSnid.w_snid   = SNID;

				Status = DsIoctl( hStream, N_SETSNID, &SetSnid, sizeof SetSnid, &Error );
				if ( NT_ERROR( Status ) || Error != 0 )
				{
					DigiDump( DIGIERRORS, ("DsOpen failed, Status 0x%x, Error 0x%x\n", 
					                      Status, Error ) );
					Status = STATUS_UNSUCCESSFUL;
					goto IwanError;
				}
			}

			// Send wan registration
			{
				{
					struct wan_reg WanReg = {0};
					WanReg.wan_type = WAN_REG;
					WanReg.wan_snid = SNID;
					Status = DsPutMsg( hStream, &WanReg, sizeof WanReg, NULL, 0, DS_PRIORITY_LOW );
					if ( !NT_SUCCESS( Status ) )
					{
						DigiDump( DIGIERRORS, ("Wan Registration PutMsg failed\n") );
						goto IwanError;
					}
				}	
				
				// Did wan registration succeed?
				{
					struct wan_reg WanReg = {0};
					ULONG cbCtrl, cbData;
					Status = DsGetMsg( hStream, &WanReg, sizeof WanReg, &cbCtrl, NULL, 0, &cbData, DS_PRIORITY_HIGH );
					if ( !NT_SUCCESS( Status ) )
					{
						DigiDump( DIGIERRORS, ("Wan Registration GetMsg request failed, Status 0x%x\n", Status) );
						goto IwanError;
					}
					
					if ( cbCtrl != sizeof WanReg || cbData != 0 ||
					     WanReg.wan_type != WAN_REG || 
					     WanReg.wan_status != WAN_SUCCESS )
					{
						DigiDump( DIGIERRORS, 
						          ("Wan Registration failed, type 0x%x, status 0x%x\n", 
		                      WanReg.wan_type, WanReg.wan_status ) );
						goto IwanError;
					}
				}
			}

			// Send wc_connect
			{
				{
					struct wan_ctl WanCtl = {0};
					WanCtl.wan_type    = WAN_CTL;
					WanCtl.wan_command = WC_CONNECT;
					WanCtl.wan_remtype = WAN_TYPE_ASC;
					WanCtl.wan_remsize = '0';
					Status = DsPutMsg( hStream, &WanCtl, sizeof WanCtl, NULL, 0, DS_PRIORITY_HIGH );
					if ( !NT_SUCCESS( Status ) )
					{
						DigiDump( DIGIERRORS, ("WC_CONNECT PutMsg failed\n") );
						goto IwanError;
					}
				}	
				
				// Did WC_CONNECT succeed?
				{
					struct wan_ctl WanCtl = {0};
					ULONG cbCtrl, cbData;
					Status = DsGetMsg( hStream, &WanCtl, sizeof WanCtl, &cbCtrl, NULL, 0, &cbData, DS_PRIORITY_HIGH );
					if ( !NT_SUCCESS( Status ) )
					{
						DigiDump( DIGIERRORS, ("WC_CONNECT GetMsg request failed\n") );
						goto IwanError;
					}
					
					if ( cbCtrl != sizeof WanCtl || cbData != 0 ||
					     WanCtl.wan_command != WC_CONCNF || 
					     WanCtl.wan_status != WAN_SUCCESS )
					{
						DigiDump( DIGIERRORS, ("WC_CONNECT GetMsg returned junk\n") );
						goto IwanError;
					}
				}
			}

IwanError:
			if ( NT_ERROR( Status ) && gStreams[i].hStream != NULL )
			{
				DsClose( hStream );
				goto ExitError;
			}
		}
#undef SNID
#endif
	}
	DigiDump( DIGIFLOW, ( "Leave OpenLinks\n" ) );
	return( STATUS_SUCCESS );

ExitError:;
	DsCloseDevice( ghDevice );
	return( STATUS_UNSUCCESSFUL );
}



NTSTATUS
RunStress()
/*++

Description:

	This routine starts up data transfer on all links and waits for bStop flag to
	be set (currently can only be done in the debugger).

Parameters:

	None

Return Value:

	None

--*/
{
	ULONG i, j;
	NTSTATUS Status;
	HANDLE hThread;

	DigiDump( DIGIFLOW, ("Enter RunStress\n") );

	// Alloc data buffers for all streams

	for ( i = 0; i < NSTREAMS; i++ )
	{
		// Alloc PutMsg buffers
		for ( j = 0; j < NPUTS; j++ )
		{
			KeInitializeEvent( &gStreams[i].Put.Events[j], SynchronizationEvent, TRUE );
			gStreams[i].Put.Buffers[j] = DigiAllocMem( NonPagedPool, NPUTBYTES );
			if ( !gStreams[i].Put.Buffers[j] )
			{
				DigiDump( DIGIERRORS, 
					("*** Couldn't alloc PutMsg buffer.  i 0x%x, j 0x%x\n", i, j) );
				return( STATUS_UNSUCCESSFUL );
			}
		}

		// Alloc GetMsg buffers
		for ( j = 0; j < NGETS; j++ )
		{
			KeInitializeEvent( &gStreams[i].Get.Events[j], SynchronizationEvent, TRUE );
			gStreams[i].Get.Buffers[j] = DigiAllocMem( NonPagedPool, NGETBYTES );
			if ( !gStreams[i].Get.Buffers[j] )
			{
				DigiDump( DIGIERRORS, 
					("*** Couldn't alloc GetMsg buffer.  i 0x%x, j 0x%x ***\n", i, j) );
				return( STATUS_UNSUCCESSFUL );
			}
		}

		// Initialize spinlocks
		KeInitializeSpinLock( &gStreams[i].Put.Lock );
		KeInitializeSpinLock( &gStreams[i].Get.Lock );

		// Start GetMsg pump
		Status = PsCreateSystemThread( &hThread, THREAD_ALL_ACCESS, NULL, 
		                               NULL, NULL, GetMsgPump, (HANDLE)i );
		if ( Status != STATUS_SUCCESS )
		{
			DigiDump( DIGIERRORS, ("*** Error creating thread!\n") );
			return( STATUS_UNSUCCESSFUL );
		}
		Status = ObReferenceObjectByHandle( hThread, THREAD_ALL_ACCESS, NULL, 
		                                    KernelMode, &ghThreads[i][0], NULL );
		if ( Status != STATUS_SUCCESS )
		{
			DigiDump( DIGIERRORS, ("*** Error referencing thread!\n") );
			return( STATUS_UNSUCCESSFUL );
		}

		// Start PutMsg pump
		Status = PsCreateSystemThread( &hThread, THREAD_ALL_ACCESS, NULL, 
		                               NULL, NULL, PutMsgPump, (HANDLE)i );
		if ( Status != STATUS_SUCCESS )
		{
			DigiDump( DIGIERRORS, ("*** Error creating thread!\n") );
			return( STATUS_UNSUCCESSFUL );
		}
		Status = ObReferenceObjectByHandle( hThread, THREAD_ALL_ACCESS, NULL, 
		                                    KernelMode, &ghThreads[i][1], NULL );
		if ( Status != STATUS_SUCCESS )
		{
			DigiDump( DIGIERRORS, ("*** Error referencing thread!\n") );
			return( STATUS_UNSUCCESSFUL );
		}
	}

	DigiDump( DIGIFLOW, ( "Leave RunStress\n" ) );
	return( STATUS_SUCCESS );
}



VOID
Cleanup()
/*++

Description:

	Shutdown stress test.

Parameters:

	None

Return Value:

	None

--*/
{
	ULONG i, j;
	LARGE_INTEGER Timeout;
	NTSTATUS Status;

	DigiDump( DIGIFLOW|DIGICLEANUP, ( "Enter Cleanup\n" ) );
	bStop = TRUE;

	Timeout.QuadPart = 5000 * -10000; // 5 seconds
	for ( i = 0; i < NSTREAMS; i++ )
	{
		// Wait for PutMsgThread to terminate
		if ( ghThreads[i][1] )
		{
			DigiDump( DIGICLEANUP, ("Waiting for PutMsg Thread on stream 0x%x\n", i) );
			Status = KeWaitForSingleObject( ghThreads[i][1], Executive, KernelMode, 
			                                FALSE, &Timeout );
			ASSERT( Status == STATUS_WAIT_0 );
			ObDereferenceObject( ghThreads[i][1] );
			DigiDump( DIGICLEANUP, ("  -> PutMsg thread wait completed\n") );
		}
		// Wait for GetMsgThread to terminate
		if ( ghThreads[i][0] )
		{
			DigiDump( DIGICLEANUP, ("Waiting for GetMsg Thread on stream 0x%x\n", i) );
			Status = KeWaitForSingleObject( ghThreads[i][0], Executive, KernelMode, 
			                                FALSE, &Timeout );
			ASSERT( Status == STATUS_WAIT_0 );
			ObDereferenceObject( ghThreads[i][0] );
			DigiDump( DIGICLEANUP, ("  -> GetMsg thread wait completed\n") );
		}

		DigiDump( DIGICLEANUP, ("Cleanup: Shutting down\n") );

		// Shutdown Stream
		if ( gStreams[i].hStream )
		{
			DsPurge( gStreams[i].hStream, DS_PURGE_ALL );
			DsClose( gStreams[i].hStream );
		}

		// Free GetMsg buffers
		for ( j = 0; j < NGETS; j++ )
			if ( gStreams[i].Get.Buffers[j] )
				DigiFreeMem( gStreams[i].Get.Buffers[j] );

		// Free GetMsg buffers
		for ( j = 0; j < NPUTS; j++ )
			if ( gStreams[i].Put.Buffers[j] )
				DigiFreeMem( gStreams[i].Put.Buffers[j] );
	}

	DsCloseDevice( ghDevice );

	DigiDump( DIGIFLOW|DIGICLEANUP, ("Leave Cleanup\n") );
	return;
}


