/*
*******************************************************************************
* Copyright (c) 1996-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 copyright work  Digi   International *
* Inc.    Except   as   permitted   by  federal  law,  17 USC 117, copying is *
* strictly prohibited.                                                        *
*                                                                             *
* Digi  International  Inc.  CONFIDENTIAL  -   (Digi    International    Inc. *
* Confidential  when   combined   with   the   aggregated  modules  for  this *
* product) OBJECT CODE ONLY SOURCE MATERIALS                                  *
*                                                                             *
* (C) COPYRIGHT Digi International Inc. 1996-97                               *
*                                                                             *
* All Rights Reserved                                                         *
*                                                                             *
* US   Government   Users   Restricted   Rights   -   Use,   duplication   or *
* disclosure  restricted by GSA ADP Schedule Contract with Digi International *
* Inc.                                                                        *
*                                                                             *
*******************************************************************************

// Tracer.cpp implementation file

Implements tracing functionality for LAPB, X25 and FR protocols

to start a trace one only need to to construct a CTracer derived object
check Fail() to see if the trace started; an exception is not thrown

destruct object to stop trace

Pause() will cancel all completed but unprocessed ioctl's and purge requests
the driver has

Revision History:

   $History: tracer.cpp $
 * 
 * *****************  Version 20  *****************
 * User: Mikez        Date: 7/11/97    Time: 5:03p
 * Branched in $/Components/Windows/NT/Sync/Build/Diag
 * 
 * *****************  Version 19  *****************
 * User: Mikez        Date: 7/10/97    Time: 4:58p
 * Updated in $/Components/Windows/NT/Sync/Build/diag
 * Trace was sending GetMsgs with null control and data buffer pointers,
 * causing all GetMsg requests to be failed.
 * 
 * *****************  Version 18  *****************
 * User: Mikez        Date: 7/07/97    Time: 11:45a
 * Updated in $/Components/Windows/NT/Sync/rev2diag
 * Updated to support DStreams API.
 * 
 * *****************  Version 17  *****************
 * User: Rodgerb      Date: 2/25/97    Time: 1:24p
 * Updated in $/Components/Windows/NT/Sync/rev2diag
 * 
 * *****************  Version 16  *****************
 * User: Rodgerb      Date: 1/08/97    Time: 5:41p
 * Updated in $/Components/Windows/NT/Sync/rev2diag
 * documentation, readability
 * improved error handling
*/

#include "stdafx.h"
#include "Diagnostic.h"
#include "tracer.h"

// each CSXB_USER_GETMSG is a self contained ioctl with data for 
// async DeviceIOControl calls
CSXB_USER_GETMSG::CSXB_USER_GETMSG(PVOID hStream) : CSXB_IOCTL()
{
	ZeroMemory( &GetMsg, sizeof( DS_GETMSG ) );
	GetMsg.hStream = hStream;
	GetMsg.pControl = &ControlBuffer;
	GetMsg.cbControl = 0;
	GetMsg.cbMaxControl = c_MaxControlLength;
	GetMsg.pData = &DataBuffer;
	GetMsg.cbData = 0;
	GetMsg.cbMaxData = c_MaxDataLength;

	Ol.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
	Ol.Internal = 0;
	Ol.InternalHigh = 0;
	Ol.Offset = 0;
	Ol.OffsetHigh = 0;
}

CSXB_USER_GETMSG::~CSXB_USER_GETMSG()
{
	CloseHandle(Ol.hEvent);
}

// static cause each CTrace didn't need it's own copy
int CTracer::m_EventSize;

CTracer::CTracer(CDataSource *ds, eSourceType Type)
	: m_PrintfQueueEvent(FALSE, TRUE)
{
// get user defined number of ioctl to create and send down to driver
	m_EventSize = AfxGetApp()->GetProfileInt(
		c_pszKeySettings,
		c_pszValueEventSize,
		cDefaultEventSize);

// basic init stuff
	ASSERT(m_DataSource != NULL);
	m_DataSource = ds; // frames are sent off here

	m_Snid = m_DataSource->GetSNID();

	m_Fail = ERROR_SUCCESS;
	m_Thread = NULL;
	m_VCObject = NULL;
	m_SwitchOn = NULL;
	m_SwitchOff = NULL;


// open file handle to driver
	m_hFile = CreateFile(	m_DataSource->GetDeviceName(),
												GENERIC_READ | GENERIC_WRITE,
												FILE_SHARE_READ | FILE_SHARE_WRITE,
												NULL,
												CREATE_ALWAYS,
												FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, 
												NULL );

// handle failure
	m_Fail = GetLastError();
	if (m_Fail != ERROR_SUCCESS)
	{
// destructor will clean up
		return;
	}

// open stream object
	SOpen = new CSOpen(Type);

// We should be able to complete the open within the I/O timeout period.
// If not we assume the firmware has hung.
	m_bDeviceOpened = DeviceIoControl(SOpen,IOCTL_TIMEOUT);
   
// handle failure
	if (!m_bDeviceOpened)
	{
// destructor will clean up
		m_Fail = TRUE;
		return;
	}

	m_VCObject = SOpen->GetVCObject();
	if (m_VCObject == NULL) 
	{
		m_Fail = TRUE;
		m_bDeviceOpened = FALSE;
		return;
	}

// start up thread that sends down and processes ioctl's (frames)
// it wait til trace starts
	m_Thread = AfxBeginThread(PrintfThread, this, THREAD_PRIORITY_NORMAL);

	TRY
	{
	// create ioctl's (frames)
		m_Query.SetSize(m_EventSize);
		for (int i = 0; i < m_EventSize; i++)
		{
			PCQUEUE_INFO Info = (PCQUEUE_INFO)new CSXB_USER_GETMSG(m_VCObject);

			m_Query.SetAt(i, Info);
		}
	}
	CATCH(CMemoryException, Mem)
	{
		m_Fail = TRUE;
// destructor will clean up
	}
	END_CATCH
}

BOOL CTracer::ThrowSwitch(CSXB_USER_IOCTL *pSwitch, eSwitch Pos)
{

	int SwitchReturnCode;

// sends down a user ioctl
	if (m_VCObject != NULL)
	{
		pSwitch->SetVCObject(m_VCObject);
		if (!DeviceIoControl(pSwitch,IOCTL_TIMEOUT))
		{
			m_Fail = GetLastError();
		} else
		{
			SwitchReturnCode = pSwitch->GetReturnCode();
			if (Pos == sOn)
			{
				m_SwitchOn = NULL;
			}
			else
			{
				m_SwitchOff = NULL;
			}
			delete pSwitch;
		}
	}

// start PrintfThread thread routine
	m_PrintfQueueEvent.SetEvent();

	return m_Fail == ERROR_SUCCESS && SwitchReturnCode == 0;
}

CFRTracer::CFRTracer(CDataSource *ds) : CTracer(ds, stFR)
{
// if no failure then turn trace on
	if (!Fail())
	{
		m_SwitchOn = new CFRTraceSwitch(m_Snid);

		ThrowSwitch(m_SwitchOn, sOn);
	}
}

CLAPBTracer::CLAPBTracer(CDataSource *ds) : CTracer(ds, stLAPB)
{
// if no failure then turn trace on
	if (!Fail())
	{
		m_SwitchOn = new CLAPBTraceSwitch(m_Snid);

		ThrowSwitch(m_SwitchOn, sOn);
	}
}

CX25Tracer::CX25Tracer(CDataSource *ds) : CTracer(ds, stX25)
{
// if no failure then turn trace on
	if (!Fail())
	{
		m_SwitchOn = new CX25TraceSwitch(m_Snid);

		ThrowSwitch(m_SwitchOn, sOn);
	}
}

CTracer::~CTracer()
{
// kill thread that puts down requests to driver
	m_Fail = 1;
	if (m_Thread)
		::WaitForSingleObject(m_Thread->m_hThread, INFINITE);
	m_Thread = NULL;

	if (m_VCObject)
	{
// have driver complete and dispose of requests it has
		CPurgeRequest Purge(m_VCObject);
		DeviceIoControl(&Purge,IOCTL_TIMEOUT);
	}

// If the Device was opened then close it
	if (m_bDeviceOpened)
	{
		CSClose Close(m_VCObject);
		DeviceIoControl(&Close,IOCTL_TIMEOUT);
	}

// close file handle
	if (m_hFile)
		CloseHandle(m_hFile);

// dispose of all ioctl's
	for (int i = 0; i < m_Query.GetSize(); i++)
		delete m_Query[i];
	for (i = 0; i < m_Process.GetSize(); i++)
		delete m_Process[i];

// delete the Switch IOCTL's if they exists
	if (m_SwitchOn)
		delete m_SwitchOn;
	if (m_SwitchOff)
		delete m_SwitchOff;
}

CFRTracer::~CFRTracer()
{
// turn trace off
	m_SwitchOff = new CFRTraceSwitch(m_Snid, sOff);

	ThrowSwitch(m_SwitchOff, sOff);
}

CLAPBTracer::~CLAPBTracer()
{
// turn trace off
	m_SwitchOff = new CLAPBTraceSwitch(m_Snid, sOff);

	ThrowSwitch(m_SwitchOff, sOff);
}

CX25Tracer::~CX25Tracer()
{
// turn trace off
	m_SwitchOff = new CX25TraceSwitch(m_Snid, sOff);

	ThrowSwitch(m_SwitchOff, sOff);
}

BOOL CTracer::DeviceIoControl(CSXB_IOCTL *IOCTL,int iTimeout)
{
// async call to DeviceIOControl
	CEvent event(FALSE, TRUE);
	OVERLAPPED thisOl = { 0, 0, 0, 0, event };

// send it down
	IOCTL->DeviceIoControl(m_hFile, &thisOl);

	CSingleLock devIOComplete(&event);
	BOOL result = devIOComplete.Lock(iTimeout);

// did it complete successfully
	DWORD dwBW = 0;
	result = result & GetOverlappedResult(m_hFile, &thisOl, &dwBW, FALSE);

	return result && dwBW > 0;
}

UINT __cdecl CTracer::PrintfThread( LPVOID ThreadContext )
{
	CTracer *Tracer = (CTracer *)ThreadContext;

// wait for trace to start
	CSingleLock lock(&Tracer->m_PrintfQueueEvent);
	lock.Lock();

// did CTracer constructor fail
	if (Tracer->Fail())
		return FALSE;

// internal count of failed DeviceIOControl calls
#ifdef _DEBUG
	DWORD failures = 0;
#endif

// loop forever
	do
	{
// send all available ioctl down to driver
		Tracer->PumpTrace();

// wait for half of ioctl send down to complete
		do
		{
// ioctl always complete in order they were sent down
			DWORD result = ::WaitForSingleObject(Tracer->m_Process[0]->Ol.hEvent, IOCTL_TIMEOUT);

// unrecoverable error or CTracer has asked this thread to terminate
			if (Tracer->Fail() || result == WAIT_FAILED)
			{
				TRACE1("failed DeviceIOControl = %d\n", failures);
				return FALSE;
			}

// rewait, timeout allow this thread to terminate in a timely fashion
			if (result == WAIT_TIMEOUT)
				continue;

			ASSERT(result == WAIT_OBJECT_0);
// make sure ioctl completed and returned some data
#ifdef _DEBUG
			DWORD dwBytes;
			BOOL golr = GetOverlappedResult(Tracer->m_hFile,
											&Tracer->m_Process[0]->Ol,
											&dwBytes,
											FALSE);

			if (dwBytes == 0 || !golr)
				failures++;
#endif

			Tracer->FinishPump();
		}
		while (Tracer->m_Process.GetSize() > m_EventSize / 2);
	} 
	while (TRUE);

// never happens
	return (TRUE);
}  // end PrintfThread

void CTracer::PumpTrace()
{
// send all available ioctl to the driver
	while (m_Query.GetSize() > 0)
	{
// take first available ioctl
		PCQUEUE_INFO Info = m_Query[0];
		ASSERT(Info);

// reset it for use
		ASSERT(m_VCObject == Info->GetMsg.hStream);
		Info->GetMsg.cbControl = 0;
		Info->GetMsg.cbData = 0;
		
// send it down
		BOOL result = Info->DeviceIoControl(m_hFile);

		if (!result)
		{
			DWORD Error = GetLastError();

// if it fails to go down resend it
			if (Error != ERROR_IO_PENDING)
			{
				TRACE1("DeviceIOControl failed, error %d", Error);
				continue;
			}
		}

// move ioctl fromn "available" queue to "sent down to driver" queue
		m_Query.RemoveAt(0);
		m_Process.Add(Info);
	}
}

void CTracer::FinishPump()
{
// pull off completed ioctl from "sent down to driver" queue
	PCQUEUE_INFO Info = m_Process[0];
	ASSERT(Info);
	
// reset it for use
	ASSERT(m_VCObject == Info->GetMsg.hStream);
	Info->Ol.Offset = 0;
	Info->Ol.OffsetHigh = 0;

// make sure we received data
	if (Info->GetMsg.cbControl > 0 && Info->GetMsg.cbData > 0)
	{
// package data
		strbuf GetControl = { c_MaxControlLength,
													Info->GetMsg.cbControl,
													Info->ControlBuffer
												};

		strbuf GetData =	{	c_MaxDataLength,
												Info->GetMsg.cbData,
												Info->DataBuffer
											};

// process it, put in frame buffer, bump frame counts, etc...
		m_DataSource->ProcessFrame(GetControl, GetData);
	}

// put it on the "available" queue
	m_Process.RemoveAt(0);
	m_Query.Add(Info);
}

void CTracer::Suspend()
{ 
	ASSERT(m_Thread != NULL);

// kill thread that puts down requests to driver
	m_Fail = 1;
	if (m_Thread)
		::WaitForSingleObject(m_Thread->m_hThread, INFINITE);
	m_Thread = NULL;
	m_Fail = 0;

// have driver complete and dispose of requests it has
	if (m_VCObject)
	{
		CPurgeRequest Purge(m_VCObject);

		DeviceIoControl(&Purge,IOCTL_TIMEOUT);
	}

// reset all ioctl sent down to driver for use
	for (short i = 0; i < m_Process.GetSize(); i++)
		ResetEvent(m_Process[i]->Ol.hEvent);

// move all ioctl to "available" queue
	m_Query.Append(m_Process);
	m_Process.SetSize(0);

	ASSERT(m_Query.GetSize() == m_EventSize);
}

void CTracer::Resume()
{ 
// restart thread that put ioctls down
	ASSERT(m_Thread == NULL);

	m_Thread = AfxBeginThread(PrintfThread, this, THREAD_PRIORITY_NORMAL);
}
