/*
$Module NX.C$

Copyright 1990
By NetFRAME Systems Inc.
   Sunnyvale, California U.S.A.

$Author:   Karl S. Johnson  $
$Date:   03 Apr 1990 11:54:02  $
$Revision:   1.2  $

$Description$
This is a NetWare Loadable Module to measure network performance.
$EndDescription$


   Revision History
$Log:   H:/386/NLMS/DX/SRC/VCS/NX.C  $
 * 
 *    Rev 1.2   03 Apr 1990 11:54:02   Karl S. Johnson
 * Recode scheduler in exerciser routine to clean up and to go to
 *   sleep when there is nothing to do.
 * 
 *    Rev 1.1   02 Apr 1990 17:15:32   Karl S. Johnson
 * Various changes to make it work the first time on System Pro and NetFRAME
 * 
 *    Rev 1.0   21 Mar 1990 10:48:46   Walter A. Wallach
 * Initial revision.
 * 
*/
#include "procdefs.h"
#include "ecb.h"
#include "loader.h"
#include "lanconf.h"
#include "random.h"
#define MAX_DEVICES 16
#define MAX_SENDS_PER_DEVICE 32
#define DEVICES_PER_PAGE 10
#define NX_PRIORITY 50
#define NX_SIGNATURE "NX TEST PACKET"

#define STACK_SIZE 2048*16
#define _UNUSED(x) if (0) if (x)

extern LONG NumberOfPollingLoops, MaximumNumberOfPollingLoops;

LONG NXUpdateInterval;
ScreenStruct *nXerciseScreen;
int DeviceCount = 0;
BYTE *stack;			/* NX Monitor Stack */
LONG stackSize;
BYTE *NXStack = NULL;		/* NX Exerciser Stack */
LONG NXActualStackSize;
LONG nXerciseModuleHandle;
LONG ServerProcessPriority = 50;
LONG NXMonitorProcessID = 0;
LONG NXerciseProcessID = 0;
LONG NXChunksPerIO;
LONG NXMaxSendsPerDevice;
LONG NXSleeping = 0;
LONG NXWakeRequested = 0;
char NXMonitorStatus = 'I'; /* Possible Status I=Initializing R=Running P=Pending Stop S=Stopped */
eventcontrolblock *ECBList = NULL;
struct DeviceData
    {
    int OutstandingIOs;
    int Index;
    int BoardNumber;
    int RecieverRegistered;
    LONG Random;
    BYTE ActivityType;		/* 'T' = Transmit 'R' = Recieve 'B' = Both */
    BYTE Name[80];
    } Device[MAX_DEVICES];
    
/* NOTE: In order to prevent overflow of certain performance counters, byte
   counts are maintained in units of 64 bytes (1 * 2^6) called "Chunks".
   To convert Chunks to Kilobytes divide by 16.
 */
#define CHUNK_SIZE 64
#define CHUNK_TO_KB 16
#define MAX_PACKET_CHUNKS ( 1500 / CHUNK_SIZE ) 
struct PerformanceData
    {
	LONG TotalReceivesCompleted;
	LONG TotalChunksReceived;
	LONG CurrentReceivesCompleted;
	LONG CurrentChunksReceived;
	LONG TotalSendsCompleted;
	LONG TotalChunksSent;
	LONG CurrentSendsCompleted;
	LONG CurrentChunksSent;
    } Performance[MAX_DEVICES];

struct PerformanceData Aggregate;
int AggregateOutstandingIOs = 0;

int BoardToDeviceIndex[MAX_DEVICES];

BYTE CommonBuffer[2000];
/* debug */
eventcontrolblock *SendECB[MAX_DEVICES*32];
LONG SendECBCount= 0;
/* debug */

void
NXSendCompletion( eventcontrolblock *currentECB );

LONG
NXReceiveCompletion( eventcontrolblock *currentECB );

void
MakeDeviceDescription( LONG BoardNumber, BYTE *buffer);

void
NXercise();

void
NXMonitor();

/* LSL Interface routines */
LONG CLSLRegisterPreScanStack( int BoardNumber,
	    	    	    	int HandlerProcedure(),
	    	    	    	int ControlProcedure() );

LONG CLSLDeRegisterPreScanStack( int BoardNumber );

void *CLSLGetECB();

void CLSLReturnECB( void *ECB );

extern NXReceiveDone();
extern NXSendDone();
extern NXControl();

LONG
StartProcedure(
		LONG moduleHandle,
		ScreenStruct *initializationErrorScreen,
		BYTE *commandLine,
		BYTE *loadDirectoryPath,
		LONG unitializedDataLength,
		LONG fileHandle,
		LONG (*ReadRoutine)(LONG handle, LONG offset, BYTE *buffer, LONG length),
		LONG customDataOffset,
		LONG customDataSize)
{
	static BYTE nXerciseScreenName[] = "NX Screen";
	int i;
	BYTE *Token;
	_UNUSED(commandLine);
	_UNUSED(loadDirectoryPath);
	_UNUSED(unitializedDataLength);
	_UNUSED(fileHandle);
	_UNUSED(ReadRoutine);
	_UNUSED(customDataOffset);
	_UNUSED(customDataSize);

	for (Token = commandLine; *Token != '\000'; Token++)
	    {
	    if (*Token == 'D') EnterDebugger();
	    }

	for ( i = 0; i < MAX_DEVICES; i++ ) BoardToDeviceIndex[i] = -1;

	CSetB( 0, CommonBuffer, sizeof( CommonBuffer ) );
	CStrCpy( CommonBuffer, NX_SIGNATURE );
		
	stack = GetNonMovableMemory(STACK_SIZE, &stackSize);
	if (stack == NULL)
	    {
	    OutputToScreen(initializationErrorScreen,
			"NX: Unable to get memory for stack\r\n");
	    goto Error0;
	    }

	nXerciseModuleHandle = moduleHandle;
	if (OpenScreen(nXerciseScreenName, &nXerciseScreen) != 0)
	    {
	    OutputToScreen(initializationErrorScreen,
			"NX: Unable to open NX screen\r\n");
	    goto Error1;
	    }

	/* The variable stackSize contains the amount of memory actually
	   allocated for the stack, so use it instead of STACK_SIZE.*/

	/* this should be the last thing we do */
	NXMonitorProcessID = CCreateProcess(NX_PRIORITY, NXMonitor,
			stack + stackSize, stackSize, "NXMon");
	while ( NXMonitorStatus == 'I') CRescheduleLast();
	switch ( NXMonitorStatus)
	    {
	    case 'R':
		break;
	    case 'S':
	    default:
		OutputToScreen( systemConsoleScreen,
			    "Failed to start NX monitor process\r\n" );
		goto Error3;
	    }
	return (0);

/* Error Recovery */
Error3:
	CloseScreen(nXerciseScreen);
Error1:
	ReturnNonMovableMemory(stack);
Error0:
	return (-1);
}

void ExitProcedure(void)
{
	int i;
    	eventcontrolblock *ECBp;
	
	if ( NXMonitorProcessID != 0 ) CDestroyProcess( NXMonitorProcessID );

	if ( NXMonitorStatus != 'S' ) /* if not already stopped - stop */
	    {
	    NXMonitorStatus = 'S';
	    while ( AggregateOutstandingIOs != 0 ) CRescheduleLast();
	    if ( NXerciseProcessID != 0 ) CDestroyProcess( NXerciseProcessID );
	    }

	CloseScreen( nXerciseScreen );
        
        for (i=0; i < DeviceCount; i++)
	    {
            if ( Device[i].RecieverRegistered )
	        {
	    	CLSLDeRegisterPreScanStack (Device[i].BoardNumber);
	    	}
	    while ( ECBList != NULL )
	        {
		ECBp = ECBList;
		ECBList = ECBp->RLink;
/* debug */ 	if ( (LONG)ECBList == 0xFFFFFFFF ) EnterDebugger();
		/* Return the ECB to the OS */
		CLSLReturnECB( ECBp );
		}
	    }
	if ( NXStack != NULL ) ReturnNonMovableMemory( NXStack );
	ReturnNonMovableMemory( stack );

}

void NXExit(void)
{
	KillMe((struct LoadDefinitionStructure *)nXerciseModuleHandle);

	/* Sleep forever until the exit procedure kills this process */
	for (;;)
		CSleepUntilInterrupt();
}

void
NXMonitor()
{
    long int LStatus;
    int DeviceIndex;
    LONG MaxPage;
    LONG CurrentPage;
    LONG FirstDevice;
    LONG LastDevice;
    LONG StartTime;
    LONG IntervalStartTime;
    LONG ElapsedSeconds;
    LONG IntervalSeconds;
    LONG Tenths;
    LONG utilization;
    LONG BytesPerIO;
    LONG DeviceID;
    LONG UpdateTicks;
    eventcontrolblock *ECBp;
    int i;
    LONG chunks;
    LONG packets;
    BYTE Answer;
    BYTE Dummy;
    BYTE buffer[200];

    RandomNumberCount = sizeof( RandomNumber ) / 4;
    /* Activate our screen */
    Enable();
    ActivateScreen(nXerciseScreen);

    /* Tell NXLoad we are Running */
    NXMonitorStatus = 'R';

    /* Ask for packet size */
    InputFromScreen( nXerciseScreen,
		"RF",
		2,
		2,
		buffer,
		0L,
		TRUE,
		"F",
		"Packet Length Type [F=Fixed R=Random]? " );
    if ( buffer[0] == 'F' || buffer[0] == 'f' )
        {
	PromptForUnsignedNumber( nXerciseScreen,
			     &BytesPerIO,
			     CHUNK_SIZE,
			     MAX_PACKET_CHUNKS * CHUNK_SIZE,
			     10L,
			     0L,
			     TRUE,
			     64L,
			     "Bytes per packet [%d:%d]? ",
    	    	    	     CHUNK_SIZE,
			     MAX_PACKET_CHUNKS * CHUNK_SIZE );
	NXChunksPerIO = ( BytesPerIO + ( CHUNK_SIZE / 2 )) / CHUNK_SIZE;
	if ( ( NXChunksPerIO * CHUNK_SIZE ) != BytesPerIO )
	    {
	    OutputToScreen( nXerciseScreen,
			"Closest packet size avaiable %d selected\n\r",
			NXChunksPerIO * 64 );
	    }
	}
    else
        {
    	NXChunksPerIO = 0;
	}

    /* Ask for queue depth */
    PromptForUnsignedNumber( nXerciseScreen,
			     &NXMaxSendsPerDevice,
			     1L,
			     MAX_SENDS_PER_DEVICE,
			     10L,
			     0L,
			     TRUE,
			     3L,
			     "Number of concurrent sends to queue per device [1-%d]? ",
                             MAX_SENDS_PER_DEVICE);

    /* Ask for update interval */
    PromptForUnsignedNumber( nXerciseScreen,
			     &NXUpdateInterval,
			     1L,
			     60L,
			     10L,
			     0L,
			     TRUE,
			     5L,
			     "Screen update interval in seconds [1-60]? ");
    ConvertSecondsToTicks( NXUpdateInterval, 0L, &UpdateTicks );

    /* Ask for selections or go */
    DeviceCount = 0;
    for (DeviceID = 0; DeviceID < MAX_DEVICES; DeviceID++)
	{
	/* test for presence of device */
	if ( MLIDLoadedHandleTable[DeviceID] == 0 ) continue;
	
	MakeDeviceDescription( DeviceID, Device[DeviceCount].Name);
	LStatus = PromptForYesOrNo( nXerciseScreen,
				   0L,
				   TRUE,
				   "\r\nExercise %s ?",
				   Device[DeviceCount].Name );
	if ( LStatus )
	    {
	    /* Build map to map board number to device array index */
	    BoardToDeviceIndex[DeviceID] = DeviceCount;
	    
	    InputFromScreen( nXerciseScreen,
			"TRB",
			2,
			2,
			buffer,
			0L,
			TRUE,
			"B",
			"Device Activity [T=Transmit R=Recieve B=Both]? " );

    	    Device[DeviceCount].Index = DeviceCount;
    	    Device[DeviceCount].BoardNumber = DeviceID;
    	    Device[DeviceCount].ActivityType = buffer[0];
    	    Device[DeviceCount].Random = 0;
    	    Device[DeviceCount].OutstandingIOs = 0;
	    Device[DeviceCount].RecieverRegistered = 0;

	    if ( Device[DeviceCount].ActivityType != 'T' ) /* Recieving ? */
	         {
		 /* Register stack */
		if ( CLSLRegisterPreScanStack( DeviceID, NXReceiveDone, NXControl ) == 0 )
		    {
		    Device[DeviceCount].RecieverRegistered = 1;
		    }
		else
		    {
		    OutputToScreen( nXerciseScreen, "NX Could not register PreScan reciever for device\r\n");
		    }
		 }
	    if ( Device[DeviceCount].ActivityType != 'R' ) /* Transmitting? */
	         {
    	    	for ( i = 0; i < NXMaxSendsPerDevice; i++ )
		    {
		    /* Get an ECB for sending from the OS */
		    while ( ( ECBp = CLSLGetECB() ) == NULL )
		        {
/*
		    	OutputToScreen( nXerciseScreen, "NX Could not get a send ECB - trying again\r\n");
*/
	    	    	Delay ( 5 );
	    	    	}			 

/* debug */
SendECB[SendECBCount] = ECBp;
SendECBCount++;
/* debug */
	    	    /* Link the ECB into the available queue for the device*/
		    ECBp->RLink = ECBList;
		    ECBList = ECBp;
/* debug */ 	if ( (LONG)ECBList == 0xFFFFFFFF ) EnterDebugger();
		    /* Setup the send ECB */
		    ECBp->RESRAddress = (LONG)&NXSendDone;
		    ECBp->RLogicalID = 0;
		    ECBp->RImmediateAddress[0] = 0xff;
		    ECBp->RImmediateAddress[1] = 0xff;
		    ECBp->RImmediateAddress[2] = 0xff;
		    ECBp->RImmediateAddress[3] = 0xff;
		    ECBp->RImmediateAddress[4] = 0xff;
		    ECBp->RImmediateAddress[5] = 0xff;
		    ECBp->RSocket = 0;
		    ECBp->RPacketLength = 0;
		    ECBp->RFragmentCount = 1;
		    ECBp->RPacketOffset = (LONG)CommonBuffer;
		    ECBp->RPacketSize = 0;
		    }
	    	}		
	    /* Clear performance information */
	    Performance[DeviceCount].TotalReceivesCompleted = 0;
	    Performance[DeviceCount].TotalChunksReceived = 0;
	    Performance[DeviceCount].CurrentReceivesCompleted = 0;
	    Performance[DeviceCount].CurrentChunksReceived = 0;
	    Performance[DeviceCount].TotalSendsCompleted = 0;
	    Performance[DeviceCount].TotalChunksSent = 0;
	    Performance[DeviceCount].CurrentSendsCompleted = 0;
	    Performance[DeviceCount].CurrentChunksSent = 0;

	    DeviceCount++;
	    }
	}
    /* Zero aggregate preformance numbers */

    Aggregate.TotalReceivesCompleted = 0;
    Aggregate.TotalChunksReceived = 0;
    Aggregate.CurrentReceivesCompleted = 0;
    Aggregate.CurrentChunksReceived = 0;
    Aggregate.TotalSendsCompleted = 0;
    Aggregate.TotalChunksSent = 0;
    Aggregate.CurrentSendsCompleted = 0;
    Aggregate.CurrentChunksSent = 0;
    AggregateOutstandingIOs = 0;

    /* Make sure at least one Device is selected */

    if ( DeviceCount < 1 )
	{
	OutputToScreen( nXerciseScreen,
			"No Devices selected\r\n" );
	Delay( 91 );
	NXExit();
	}

    /* Start NXercise */
    NXStack = GetNonMovableMemory( STACK_SIZE,
					    &NXActualStackSize );
    if ( NXStack == NULL)
	{
	OutputToScreen ( nXerciseScreen,
			"Insufficient memory to start NX subprocess\r\n");
	Delay( 91 );
	NXExit();
	}
    NXerciseProcessID = CCreateProcess( NX_PRIORITY - 1,
				    NXercise,
				    NXStack + NXActualStackSize,
				    NXActualStackSize,
				    "NXerci");
    /* Compute pages of displays */
    MaxPage = ( DeviceCount / DEVICES_PER_PAGE ) + 1;
    CurrentPage = 1;
    /* Clear the Screen and setup the title */
    ClearScreen( nXerciseScreen );
    PositionOutputCursor( nXerciseScreen, 0, 0 );
    OutputToScreen( nXerciseScreen,
		"NetFRAME Network Test with %d Sends queued per device  Page %d of %d",
		NXMaxSendsPerDevice,
                CurrentPage,
                MaxPage );

    /* Note the starting time for later computations */
    StartTime = CurrentTime;

    while (1)
	{
	/* Sleep for update interval */
        IntervalStartTime = CurrentTime;
	Delay( UpdateTicks );
	/* Test for keyboard key */
	if ( CheckKeyStatus( nXerciseScreen ) )
	    {
	    /* Get the key and process it */
	    GetKey( nXerciseScreen, &Dummy, &Answer, &Dummy, &Dummy, 0L );
            if ( Answer == 'Q' || Answer =='q' )
                {
	        PositionOutputCursor( nXerciseScreen, 24, 0 );
	        LStatus = PromptForYesOrNo( nXerciseScreen,
				   0L,
				   TRUE,
				   "Exit Network Exerciser? " );
	        if ( LStatus )
		    {
		    NXMonitorStatus = 'P';
		    }
                }
            else
                {
                if ( Answer > '0' && Answer <= '9' )
                    {
                    i = Answer - '0';
                    if ( i <= MaxPage )
                        {
                        CurrentPage = i;
                        ClearScreen( nXerciseScreen );
                        PositionOutputCursor( nXerciseScreen, 0, 0 );
                        OutputToScreen( nXerciseScreen,
				"NetFRAME Network Test with %d Sends queued per device  Page %d of %d",
				NXMaxSendsPerDevice,
                		CurrentPage,
                		MaxPage );
                        }
                    else RingTheBell();
                    }
                else RingTheBell();
                }    
	    }
	/* Test for requested stop */
	if ( NXMonitorStatus == 'P' )
	    {
	    while ( AggregateOutstandingIOs != 0 ) CRescheduleLast();
	    if ( NXerciseProcessID != 0 )
	    	{
		CDestroyProcess( NXerciseProcessID );
	    	NXerciseProcessID = 0;
		}
	    NXMonitorStatus = 'S';      /* Say we are stopped */
	    ReturnNonMovableMemory( NXStack );
	    NXStack = NULL;
	    NXExit();
	    }
	/* Compute and display the numbers */
	ConvertTicksToSeconds( ( CurrentTime - StartTime ),
			       &ElapsedSeconds,
			       &Tenths );
	ConvertTicksToSeconds( ( CurrentTime - IntervalStartTime ),
			       &IntervalSeconds,
			       &Tenths );
    	/* Update cumulative statistics */
	for ( DeviceIndex = 0; DeviceIndex < DeviceCount; DeviceIndex++ )
	    {
	    chunks = Performance[DeviceIndex].CurrentChunksSent;
	    Performance[DeviceIndex].TotalChunksSent += chunks;
	    Aggregate.CurrentChunksSent += chunks;
	    packets = Performance[DeviceIndex].CurrentSendsCompleted;
	    Performance[DeviceIndex].TotalSendsCompleted += packets;
	    Aggregate.CurrentSendsCompleted += packets;
	    chunks = Performance[DeviceIndex].CurrentChunksReceived;
	    Performance[DeviceIndex].TotalChunksReceived += chunks;
	    Aggregate.CurrentChunksReceived += chunks;
	    packets = Performance[DeviceIndex].CurrentReceivesCompleted;
	    Performance[DeviceIndex].TotalReceivesCompleted += packets;
	    Aggregate.CurrentReceivesCompleted += packets;
	    }
    	/* Now roll up the aggregate totals */
    	Aggregate.TotalChunksSent += Aggregate.CurrentChunksSent;
    	Aggregate.TotalChunksReceived += Aggregate.CurrentChunksReceived;
	Aggregate.TotalSendsCompleted += Aggregate.CurrentSendsCompleted;
	Aggregate.TotalReceivesCompleted += Aggregate.CurrentReceivesCompleted;

	PositionOutputCursor( nXerciseScreen, 1, 0 );
	OutputToScreen( nXerciseScreen, "%s\r\n%s\n\r",
	"    Device            Cur. Send    Cur. Recv.   Ave. Send    Ave. Recv.",
	"     Name             KB/s  Pkt/s  KB/s  Pkt/s  KB/s  Pkt/s  KB/s  Pkt/s" );
	/*                     99999 99999  99999 99999  99999 99999  99999 99999 */
        FirstDevice = ( CurrentPage - 1 ) * DEVICES_PER_PAGE;
        /* note - LastDevice is really last Device index (0 based) plus 1) */
        if ( CurrentPage == MaxPage ) 
            {
            LastDevice = DeviceCount;
            }
        else
            {
            LastDevice = CurrentPage * DEVICES_PER_PAGE;
            }
	for ( DeviceIndex = FirstDevice; DeviceIndex < LastDevice; DeviceIndex++ )
	    {
	    OutputToScreen( nXerciseScreen, "%-20.20s ", Device[DeviceIndex].Name );
	    OutputToScreen( nXerciseScreen, "%5d %5d  %5d %5d  %5d %5d  %5d %5d\r\n",
	    	( Performance[DeviceIndex].CurrentChunksSent / CHUNK_TO_KB ) / IntervalSeconds,
	    	( Performance[DeviceIndex].CurrentSendsCompleted ) / IntervalSeconds,
	    	( Performance[DeviceIndex].CurrentChunksReceived / CHUNK_TO_KB ) / IntervalSeconds,
	    	( Performance[DeviceIndex].CurrentReceivesCompleted ) / IntervalSeconds,
	    	( Performance[DeviceIndex].TotalChunksSent / CHUNK_TO_KB ) / ElapsedSeconds,
	    	( Performance[DeviceIndex].TotalSendsCompleted ) / ElapsedSeconds,
	    	( Performance[DeviceIndex].TotalChunksReceived / CHUNK_TO_KB ) / ElapsedSeconds,
	    	( Performance[DeviceIndex].TotalReceivesCompleted ) / ElapsedSeconds);
	    }
            
        /* Aggregate numbers */
	OutputToScreen( nXerciseScreen, "\r\n" );
	OutputToScreen( nXerciseScreen, "%-20.20s "," All Devices" );
	OutputToScreen( nXerciseScreen, "%5d %5d  %5d %5d  %5d %5d  %5d %5d\r\n",
		( Aggregate.CurrentChunksSent / CHUNK_TO_KB ) / IntervalSeconds,
		( Aggregate.CurrentSendsCompleted ) / IntervalSeconds,
		( Aggregate.CurrentChunksReceived / CHUNK_TO_KB ) / IntervalSeconds,
		( Aggregate.CurrentReceivesCompleted ) / IntervalSeconds,
		( Aggregate.TotalChunksSent / CHUNK_TO_KB ) / ElapsedSeconds,
		( Aggregate.TotalSendsCompleted ) / ElapsedSeconds,
		( Aggregate.TotalChunksReceived / CHUNK_TO_KB ) / ElapsedSeconds,
		( Aggregate.TotalReceivesCompleted ) / ElapsedSeconds);
	OutputToScreen( nXerciseScreen, "\r\n" );

	/* Utilization */
	utilization = 100 - ((NumberOfPollingLoops * 100 +
			(MaximumNumberOfPollingLoops >> 1)) / MaximumNumberOfPollingLoops);
	OutputToScreen( nXerciseScreen, "Server utilization %6d%%\r\n", utilization);

        /* Instructions */
	OutputToScreen( nXerciseScreen, "Q to quit or page number to view\n\r" );

        /* Zero current counts */
	for ( DeviceIndex = 0; DeviceIndex < DeviceCount; DeviceIndex++ )
	    {
	    Performance[DeviceIndex].CurrentReceivesCompleted = 0;
	    Performance[DeviceIndex].CurrentChunksReceived = 0;
	    Performance[DeviceIndex].CurrentSendsCompleted = 0;
	    Performance[DeviceIndex].CurrentChunksSent = 0;
            }
	Aggregate.CurrentReceivesCompleted = 0;
	Aggregate.CurrentChunksReceived = 0;
	Aggregate.CurrentSendsCompleted = 0;
	Aggregate.CurrentChunksSent = 0;

	}
}

void
NXercise()
{
    int Length;
    LONG LStatus;
    int CurrentDevice = 0;

    LONG SendCount = 0;
    LONG BackToBackSends = 0;
    register eventcontrolblock *ECBp;
    register struct DeviceData *DeviceP;
    struct DeviceData *LastDeviceP;
    struct DeviceData *WrapDeviceP;

    DeviceP = &Device[0];
    WrapDeviceP = &Device[DeviceCount];
    LastDeviceP = &Device[0];
    
    while (1)
	{
	if ( NXMonitorStatus != 'R' )   /* If not running go to sleep forever */
					/* and wait to be destroyed */
	    {
	    while (1) CSleepUntilInterrupt();
	    }

	if ( DeviceP->ActivityType != 'R' ) /* if T or B */
	    {
	    if ( DeviceP->OutstandingIOs < NXMaxSendsPerDevice )
		{
    	    	BackToBackSends++;
		SendCount++;

		/* Start another IO */
		if ( NXChunksPerIO == 0 )
		    {
		    /* Random Length Packets */
	
		    Length = ( ( RandomNumber[DeviceP->Random] % ( MAX_PACKET_CHUNKS - 1 ) ) + 1 ) * CHUNK_SIZE ;
		    DeviceP->Random++;
		    if ( DeviceP->Random == RandomNumberCount )
                        DeviceP->Random = 0;
		    }
		else
		    {
		    Length = NXChunksPerIO * CHUNK_SIZE;
		    }

		/* Build a physical I/O request */
    	    	ECBp = ECBList;
		ECBList = ECBp->RLink;
/* debug */ 	if ( (LONG)ECBList == 0xFFFFFFFF ) EnterDebugger();
/* debug */
    {
    int BAD, i;
    BAD = -1;
    for ( i = 0; i < SendECBCount; i++ )
        {
	if ( SendECB[i] == ECBp )
	    {
	    BAD = 0;
	    break;
	    }
	}
    if ( BAD ) EnterDebugger();
    BAD = -1;
    if ( ECBList != NULL )
        {
    	for ( i = 0; i < SendECBCount; i++ )
            {
	    if ( SendECB[i] == ECBList )
	    	{
	    	BAD = 0;
	    	break;
	    	}
	    }
    	if ( BAD ) EnterDebugger();
	}
    };
/* debug */
		ECBp->RPacketLength = Length;
		ECBp->RPacketSize = Length;
		ECBp->RFragmentCount = 1;
		ECBp->RBoardNumber = DeviceP->BoardNumber;
		ECBp->RESRBXValue = DeviceP->Index;
		DeviceP->OutstandingIOs++;
		AggregateOutstandingIOs++;
	    	/* Send the ECB */
		LStatus = CLSLSendPacket( ECBp );
    	    	if ( LStatus != 0 )
		    {
    	    	    OutputToScreen( nXerciseScreen, "Send failed - Status 0x%x" );
    		    DeviceP->OutstandingIOs--;
		    AggregateOutstandingIOs--;
		    }
		}
	    }
	/* Look at the next device */
	DeviceP++;
	if ( DeviceP == WrapDeviceP )	/* Have all devices been tried */
	    {
	    DeviceP = &Device[0];	/* Wrap the device pointer */
	    if ( SendCount == 0 )	/* Was anything send on this pass? */
	    	{
    	    	Disable();  		/* No, Nothing could be sent */
		NXSleeping = -1;	/* Going to sleep */
	    	CSleepUntilInterrupt();
		NXSleeping = 0;		/* Now awake */
		NXWakeRequested = 0;
		Enable();
		BackToBackSends = 0;	/* Note we have taken a break */
		}
	    else
	    	{   	    		/* Yes, Something was sent - keep going */
		SendCount = 0;
		}
	    }
    	if ( BackToBackSends == 20 )	/* Do we need to take a break? */
	    {
	    CRescheduleLast();		/* Give someone else a chance */
	    BackToBackSends = 0;	/* Note we have taken a break */
	    }
        }
}

void
NXSendCompletion( eventcontrolblock *currentECB )
{
    int i;
    int chunks;
    register eventcontrolblock *ECBp;

/* debug */
    int BAD = -1;
    for ( i = 0; i < SendECBCount; i++ )
        {
	if ( SendECB[i] == currentECB )
	    {
	    BAD = 0;
	    break;
	    }
	}
    if ( BAD ) EnterDebugger();
/* debug */

    i = currentECB->RESRBXValue;
    chunks = ( currentECB->RPacketLength ) / CHUNK_SIZE;
    /* Update individual device counts */
    Performance[i].CurrentSendsCompleted++;
    Performance[i].CurrentChunksSent += chunks;
    
    /* Put the request back on the send queue for the device */
    --AggregateOutstandingIOs;
    --Device[i].OutstandingIOs;
    ECBp = ECBList;
    ECBList = currentECB;
/* debug */ 	if ( (LONG)ECBList == 0xFFFFFFFF ) EnterDebugger();
    currentECB->RLink = ECBp;
    if ( NXSleeping && (!NXWakeRequested) )
    	{
	NXWakeRequested = -1;
	CRescheduleFromInterrupt(NXerciseProcessID);
    	}
}

LONG
NXReceiveCompletion( eventcontrolblock *currentECB )
{
    int i;
    int chunks;
/* the board number to device index should be computed here */
    i = BoardToDeviceIndex[currentECB->RBoardNumber];
    if ( i != -1 ) /* Do we know about this board? */
	{
    	chunks = ( currentECB->RPacketLength ) / CHUNK_SIZE;
	    
    	/* Update individual device counts */
    	Performance[i].CurrentReceivesCompleted++;
    	Performance[i].CurrentChunksReceived += chunks;
    	    
    	/* Now return the receive ECB back to the pool if it's a NX packet */
    	if ( CStrCmp( (void *)currentECB->RPacketOffset, NX_SIGNATURE ) == 0 )
	    {
	    CLSLReturnECB( (void *)currentECB );
	    return ( 0 );	/* Don't try to route the packet */
	    }
	}
    return ( 1 ); /* Attempt to route the packet to a protocol stack */
};

void
MakeDeviceDescription(
	LONG boardNumber,
	BYTE *buffer)
{
    struct DriverConfigurationStructure *lanInfo;
    BYTE *ptr;
    lanInfo = MLIDConfigurationTable[boardNumber];
    ptr = buffer;
    /* LAN name */
    sprintf(ptr, "%S", MLIDLoadedHandleTable[boardNumber]->LDFileName);
    while (*ptr != '.' && *ptr != 0) ptr++;
    *ptr++ = ' ';
                   
    *ptr++ = '[';

    if (PS2Flag)
	{
	/* slot */
	sprintf(ptr, "slot=%x ", lanInfo->DSlot);
	ptr = ptr + CStrLen(ptr);
	}
    else
	{
	/* IO ports */
	if (lanInfo->DIOPortsAndRanges[1] > 0)	/* using IO port 0 */
	    {
	    sprintf(ptr, "port=%x ", lanInfo->DIOPortsAndRanges[0]);
	    ptr = ptr + CStrLen(ptr);
	    }
	if (lanInfo->DIOPortsAndRanges[3] > 0)	/* using IO port 1 */
	    {
	    sprintf(ptr, "port=%x ", lanInfo->DIOPortsAndRanges[2]);
	    ptr = ptr + CStrLen(ptr);
	    }

	/* memory addresses */
	if (lanInfo->DMemoryDecodeAndLength[0].LANMemoryAddress > 0)	/* using memory 0 */
	    {
	    sprintf(ptr, "mem=%x ",
			lanInfo->DMemoryDecodeAndLength[0].LANMemoryAddress);
	    ptr = ptr + CStrLen(ptr);
	    if (lanInfo->DMemoryDecodeAndLength[1].LANMemoryAddress > 0)	/* using memory 1 */
		{
		sprintf(ptr, "mem=%x ",	lanInfo->DMemoryDecodeAndLength[1].LANMemoryAddress);
		ptr = ptr + CStrLen(ptr);
		}
	    }

    	/* interrupts */
	if (lanInfo->DIntLine[0] != 0xff)  			/* first interrupt */
	    {
	    sprintf(ptr, "int=%x ", lanInfo->DIntLine[0]);
	    ptr = ptr + CStrLen(ptr);
	    if (lanInfo->DIntLine[1] != 0xff)  		/* second interrupt */
		{
		sprintf(ptr, "int=%x ", lanInfo->DIntLine[1]);
		ptr = ptr + CStrLen(ptr);
		}
	    }

	/* DMA channels */
	if (lanInfo->DDMALine[0] != 0xff)				/* first DMA */
	    {
	    sprintf(ptr, "dma=%x ", lanInfo->DDMALine[0]);
	    ptr = ptr + CStrLen(ptr);
	    if (lanInfo->DDMALine[1] != 0xff)	 		/* second DMA */
		{
		sprintf(ptr, "dma=%x ", lanInfo->DDMALine[1]);
		ptr = ptr + CStrLen(ptr);
		}
	    }
	}
    *(ptr - 1) = ']';
}
