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

   dxbperf.c

Abstract:

   Perfmon extension dll support for dxb based dense adapters.

Author:

   Michael Zarns

--*/
#include <windows.h>
#include <winioctl.h>
#include <stdio.h>
#include <winperf.h>
#include "dxberr.h" // error message definition
#include "perfmsg.h"
#include "perfutil.h"
#include "dxbdata.h"
#include "dxbapi.h"

// Driver and firmware headers
#include "DsIoctl.h"
#include "dxbtypes.h"
#define MAXIOCBSZ 1024
#include "iwan_control.h"
#include "layer1_stats.h"
#define IWAN_DEVICE		"IWAN"

// Define VERY_LOUD to debug printf a decode of the data presented to perfmon.  It
// prints out everytime Collect is called and is very expensive.  Use only for 
// debugging!
//#define VERY_LOUD 1

#ifndef DBG
#define DBG 0
#endif

#if DBG
#define STATIC
#define ASSERT(x) \
   do  \
   {   \
      if (!(x)) \
      {  \
         char buf[32+sizeof(#x)+sizeof(__FILE__)];  \
         sprintf(buf, "Assert("#x") failed at ("__FILE__":%d)\n", __LINE__); \
         OutputDebugString(buf); \
      } \
   } while (0)
#else
#define STATIC static 
#define ASSERT(x) {}
#endif

static char DebugBuffer[4096];
const void 
DebugHexDump(void *p, int Length)
{
   char *Buf = p;
   char *OutBuf;
   int Count;
   while (Length)
   {
      OutBuf = DebugBuffer;
      sprintf(OutBuf, "%p: ", Buf);
      OutBuf += strlen(OutBuf);
      for (Count=0; Count<16; Count++)
      {
         if (Count<Length)
         {
            sprintf(OutBuf, "%02X ", (unsigned char)Buf[Count]);
         }
         else
         {
            sprintf(OutBuf, "   ");
         }
         OutBuf += 3;
      }
      sprintf(OutBuf, "   ");
      OutBuf += 3;
      for (Count=0; Count<16; Count++)
      {
         *OutBuf++ = ((Count<Length) ? ((Buf[Count]>30) ? Buf[Count] : '.') : ' ');
      }
      *OutBuf++ = '\n';
      *OutBuf++ = '\0';
      OutputDebugString(DebugBuffer);
      Buf += 16;
      Length -= ((Length>16) ? 16 : Length);
   }
}


static VOID 
DebugWalkData( 
	PVOID pData,
	ULONG cbData,
	ULONG nObjects )
{
	PCHAR pIn = pData;
	ULONG iObject;
	
	sprintf( DebugBuffer, "Perf Data, length 0x%x\n", cbData );
	OutputDebugString( DebugBuffer );

	for ( iObject = 0; iObject < nObjects; iObject++ )
	{
		ULONG nInstances, nCounters;
		ULONG iInstance,  iCounter;
		{
			PPERF_OBJECT_TYPE pType = (PVOID)pIn;
			sprintf( DebugBuffer, "   PerfObjectType (0x%x):\n"
			                      "      TotalByteLength      0x%x\n"
			                      "      DefinitionLength     0x%x\n"
			                      "      HeaderLength         0x%x\n"
			                      "      ObjectNameTitleIndex 0x%x\n"
			                      "      ObjectNameTitle      0x%x\n"
			                      "      ObjectHelpTitleIndex 0x%x\n"
			                      "      ObjectHelpTitle      0x%x\n"
			                      "      DetailLevel          0x%x\n"
			                      "      NumCounters          0x%x\n"
			                      "      DefaultCounter       0x%x\n"
			                      "      NumInstances         0x%x\n"
			                      "      CodePage             0x%x\n"
			                      "      PerfTime             0x%x%x\n"
			                      "      PerfFreq             0x%x%x\n",
										pType,
										pType->TotalByteLength,
										pType->DefinitionLength,
										pType->HeaderLength,
										pType->ObjectNameTitleIndex,
										pType->ObjectNameTitle,
										pType->ObjectHelpTitleIndex,
										pType->ObjectHelpTitle,
										pType->DetailLevel,
										pType->NumCounters,
										pType->DefaultCounter,
										pType->NumInstances,
										pType->CodePage,
										pType->PerfTime.HighPart, pType->PerfTime.LowPart,
										pType->PerfFreq.HighPart, pType->PerfFreq.LowPart );
			OutputDebugString( DebugBuffer );
			nCounters  = pType->NumCounters;
			nInstances = pType->NumInstances;
			pIn += sizeof *pType;
		}

		for ( iCounter = 0; iCounter < nCounters; iCounter++ )
		{
			PPERF_COUNTER_DEFINITION pCounter = (PVOID)pIn;
			sprintf( DebugBuffer, "   PerfCounterDefinition (0x%x):\n"
			                      "      ByteLength            0x%x\n"
			                      "      CounterNameTitleIndex 0x%x\n"
			                      "      CounterNameTitle      0x%x\n"
			                      "      CounterHelpTitleIndex 0x%x\n"
			                      "      CounterHelpTitle      0x%x\n"
			                      "      DefaultScale          0x%x\n"
			                      "      DetailLevel           0x%x\n"
			                      "      CounterType           0x%x\n"
			                      "      CounterSize           0x%x\n"
			                      "      CounterOffset         0x%x\n",
										 pCounter,
			                      pCounter->ByteLength,
			                      pCounter->CounterNameTitleIndex,
			                      pCounter->CounterNameTitle,
			                      pCounter->CounterHelpTitleIndex,
			                      pCounter->CounterHelpTitle,
			                      pCounter->DefaultScale,
			                      pCounter->DetailLevel,
			                      pCounter->CounterType,
			                      pCounter->CounterSize,
			                      pCounter->CounterOffset );
			OutputDebugString( DebugBuffer );
			pIn += sizeof *pCounter;
		}

		for ( iInstance = 0; iInstance < nInstances; iInstance++ )
		{
			{
				PPERF_INSTANCE_DEFINITION pInstance = (PVOID)pIn;
				sprintf( DebugBuffer, "   PerfInstanceDefinition (0x%x):\n"
				                      "      ByteLength             0x%x\n"
				                      "      ParentObjectTitleIndex 0x%x\n"
				                      "      ParentObjectInstance   0x%x\n"
				                      "      UniqueID               0x%x\n"
				                      "      NameOffset             0x%x\n"
				                      "      NameLength             0x%x\n",
											 pInstance,
				                      pInstance->ByteLength,
				                      pInstance->ParentObjectTitleIndex,
				                      pInstance->ParentObjectInstance,
				                      pInstance->UniqueID,
				                      pInstance->NameOffset,
				                      pInstance->NameLength );
				OutputDebugString( DebugBuffer );

				{
					PWSTR pName = (PVOID)( pIn + pInstance->NameOffset );
					sprintf( (PVOID)DebugBuffer, "   Instance Name (0x%x):\n"
					                             "      %S\n",
					                             pName,
					                             pName );
					OutputDebugString( DebugBuffer );
				}
				pIn += pInstance->ByteLength;
			}

			{
				ULONG Counter;
				PPERF_COUNTER_BLOCK pBlock = (PVOID)pIn;
				sprintf( DebugBuffer, "   PerfCounterBlock (0x%x):\n"
				                      "      ByteLength 0x%x\n",
				                      pBlock,
				                      pBlock->ByteLength );
				OutputDebugString( DebugBuffer );

				if ( pBlock->ByteLength < 0x100 )
				{
					OutputDebugString( "   Counter data:" );
					for ( Counter = 0; Counter < pBlock->ByteLength/4 - 1; Counter++ )
					{
						if ( !( Counter % 8 ) ) 
							OutputDebugString( "\n      " );
						sprintf( DebugBuffer, "%08x ", *(PDWORD)( pIn + sizeof pBlock + Counter*4 ) );
						OutputDebugString( DebugBuffer );
					}
					OutputDebugString( "\n" );
				}

				pIn += pBlock->ByteLength;
			}
		}
	}

	sprintf( DebugBuffer, "Final pIn 0x%x, cb printed 0x%x\n", pIn, pIn - (PCHAR)pData );
	OutputDebugString( DebugBuffer );
}


//////////////////////////////////////////
//
// LIST_ENTRY routines stolen from ntddk.h
//

#define InitializeListHead(ListHead) (\
    (ListHead)->Flink = (ListHead)->Blink = (ListHead))
    
#define IsListEmpty(ListHead) \
    ((ListHead)->Flink == (ListHead))

#define InsertTailList(ListHead,Entry) {\
    PLIST_ENTRY _EX_Blink;\
    PLIST_ENTRY _EX_ListHead;\
    _EX_ListHead = (ListHead);\
    _EX_Blink = _EX_ListHead->Blink;\
    (Entry)->Flink = _EX_ListHead;\
    (Entry)->Blink = _EX_Blink;\
    _EX_Blink->Flink = (Entry);\
    _EX_ListHead->Blink = (Entry);\
    }

#define RemoveEntryList(Entry) {\
    PLIST_ENTRY _EX_Blink;\
    PLIST_ENTRY _EX_Flink;\
    _EX_Flink = (Entry)->Flink;\
    _EX_Blink = (Entry)->Blink;\
    _EX_Blink->Flink = _EX_Flink;\
    _EX_Flink->Blink = _EX_Blink;\
    }

#define RemoveHeadList(ListHead) \
    (ListHead)->Flink;\
    {RemoveEntryList((ListHead)->Flink)}


typedef struct CHANNEL_INSTANCE
{
   PERF_INSTANCE_DEFINITION Instance;
	PWSTR                    pName;
} CHANNEL_INSTANCE, *PCHANNEL_INSTANCE;

typedef struct CSU_INSTANCE
{
	PERF_INSTANCE_DEFINITION Instance;
	PWSTR                    pName;
	PCHANNEL_INSTANCE        *pChannels;	// array of channels on this line
	DWORD                    nChannels;		// count of channel pointers in array
	PCHANNEL_INSTANCE        pTotalChannel;// Special channel for Totals per line
	DWORD                    Snid;
} CSU_INSTANCE, *PCSU_INSTANCE;

typedef struct ADAPTER_INSTANCE
{
   PERF_INSTANCE_DEFINITION Instance;
	PWSTR                    pName;
	LIST_ENTRY               ListEntry;
   DWORD                    nLines;
	PCSU_INSTANCE            *pLines;	// array of pointers to line instances
   HANDLE                   hDevice;	// Dxb device handle returned from createFile
	HANDLE                   hStream;	// Dxb stream handle, returned from ds_open
} ADAPTER_INSTANCE, *PADAPTER_INSTANCE;


DWORD gnOpens              = 0; 		// count of "Open" threads
BOOL  gInitialized         = FALSE;	// true = DLL initialized OK
DWORD gnAdapters           = 0;		// Total count of adapters

LIST_ENTRY gAdapterList;


//
// These proto's used to ensure function have correct calling format.
//
PM_OPEN_PROC    OpenDxbPerformanceData;
PM_COLLECT_PROC CollectDxbPerformanceData;
PM_CLOSE_PROC   CloseDxbPerformanceData;


STATIC VOID
DestroyChannel( PCHANNEL_INSTANCE pChannel )
{
	if ( pChannel )
	{
		if ( pChannel->pName )
			GlobalFree( pChannel->pName );
		GlobalFree( pChannel );
	}
}


STATIC PCHANNEL_INSTANCE
NewChannel( 
	PWSTR  pName, 
	ULONG  InstanceIndex, 
	ULONG  ParentTitleIndex, 
	ULONG  ParentInstance )
{
	LONG Status = ERROR_NO_SYSTEM_RESOURCES;
	PCHANNEL_INSTANCE pChannel = GlobalAlloc( GPTR, sizeof *pChannel );
	if ( !pChannel )
		goto Exit;

	pChannel->pName = GlobalAlloc( GPTR, (wcslen(pName)+1) * sizeof *pName );
	if ( !pChannel->pName )
		goto Exit;

	wcscpy( pChannel->pName, pName );
	pChannel->Instance.NameLength = (wcslen(pName)+1) * sizeof *pName;
	pChannel->Instance.NameOffset = sizeof pChannel->Instance;
	pChannel->Instance.ByteLength = sizeof pChannel->Instance + DWORD_MULTIPLE( pChannel->Instance.NameLength );

	pChannel->Instance.UniqueID = InstanceIndex;
	pChannel->Instance.ParentObjectTitleIndex = ParentTitleIndex;
	pChannel->Instance.ParentObjectInstance   = ParentInstance;

	Status = ERROR_SUCCESS;

Exit:
	if ( Status != ERROR_SUCCESS )
	{
		DestroyChannel( pChannel );
		pChannel = NULL;
	}
	return pChannel;
}


STATIC VOID
DestroyLine( PCSU_INSTANCE pLine )
{
	if ( pLine )
	{
		if ( pLine->pChannels )
		{
			ULONG i;
			for ( i = 0; i < pLine->nChannels; i++ )
				GlobalFree( pLine->pChannels[i] );
			GlobalFree( pLine->pChannels );
		}

		if ( pLine->pTotalChannel )
			GlobalFree( pLine->pTotalChannel );

		if ( pLine->pName )
			GlobalFree( pLine->pName );
		GlobalFree( pLine );
	}
}


STATIC PCSU_INSTANCE
NewLine( 
	PWSTR  pName, 
	ULONG  Snid,
	ULONG  InstanceIndex, 
	ULONG  ParentTitleIndex, 
	ULONG  ParentInstance )
{
	LONG Status = ERROR_NO_SYSTEM_RESOURCES;
	PCSU_INSTANCE pLine = GlobalAlloc( GPTR, sizeof *pLine );
	if ( !pLine )
		goto Exit;

	pLine->pName = GlobalAlloc( GPTR, (wcslen(pName)+1) * sizeof *pName );
	if ( !pLine->pName )
		goto Exit;

	pLine->Snid = Snid;

	wcscpy( pLine->pName, pName );
	pLine->Instance.NameLength = (wcslen(pName)+1) * sizeof *pName;
	pLine->Instance.NameOffset = sizeof pLine->Instance;
	pLine->Instance.ByteLength = sizeof pLine->Instance + DWORD_MULTIPLE( pLine->Instance.NameLength );

	pLine->Instance.UniqueID = InstanceIndex;
	
	// Adapter name is part of the line name, "DigiDxb2 Line1", so don't identify parent.
	//	pLine->Instance.ParentObjectTitleIndex = ParentTitleIndex;
	//	pLine->Instance.ParentObjectInstance   = ParentInstance;

	// Alloc channel instances for each "timeslot"

	pLine->nChannels = 24;
	pLine->pChannels = GlobalAlloc( GPTR, pLine->nChannels * sizeof pLine->pChannels[0] );
	if ( !pLine->pChannels )
		goto Exit;

	{
		ULONG i;
		for ( i = 0; i < pLine->nChannels; i++ )
		{
			WCHAR Name[16];
			wsprintfW( (PVOID)Name, L"Channel%02d", i+1 );
			pLine->pChannels[i] = NewChannel( Name, i, gCsuDefinition.Type.ObjectNameTitleIndex, InstanceIndex );
			if ( !pLine->pChannels[i] )
				goto Exit;
		}
	}

	// Alloc the Sum Total instance == counter sum of all channels on this line
	{
		WCHAR Name[] = L"Total";
		pLine->pTotalChannel = NewChannel( Name, pLine->nChannels, gCsuDefinition.Type.ObjectNameTitleIndex, InstanceIndex );
		if ( !pLine->pTotalChannel )
			goto Exit;
	}

	Status = ERROR_SUCCESS;

Exit:
	if ( Status != ERROR_SUCCESS )
	{
		DestroyLine( pLine );
		pLine = NULL;
	}
	return pLine;
}


STATIC VOID
DestroyAdapter( 
	PADAPTER_INSTANCE pAdapter )
{
	if ( pAdapter )
	{
		if ( pAdapter->pLines )
		{
			ULONG i;
			for ( i = 0; i < pAdapter->nLines; i++ )
				DestroyLine( pAdapter->pLines[i] );
			GlobalFree( pAdapter->pLines );
		}

		if ( pAdapter->hDevice )
		{
			if ( pAdapter->hStream )
				DsClose( pAdapter->hDevice, pAdapter->hStream );
			CloseHandle( pAdapter->hDevice );
		}

		if ( pAdapter->pName )
			GlobalFree( pAdapter->pName );
		GlobalFree( pAdapter );
	}
}


STATIC PADAPTER_INSTANCE
NewAdapter( 
	PWSTR pName, 
	ULONG InstanceIndex )
{
	LONG Status = ERROR_NO_SYSTEM_RESOURCES;
	PWSTR const pRoot = L"\\\\.\\";
	PADAPTER_INSTANCE pAdapter = NULL;
	PWSTR pFullName = GlobalAlloc( GPTR, (wcslen(pRoot)+wcslen(pName)+1)* sizeof *pName );
	if ( !pFullName )
		goto Exit;

	wcscpy( pFullName, pRoot );
	wcscat( pFullName, pName );

	pAdapter = GlobalAlloc( GPTR, sizeof *pAdapter );
	if ( !pAdapter )
		goto Exit;

	pAdapter->pName = GlobalAlloc( GPTR, (wcslen(pName)+1) * sizeof *pAdapter->pName );
	if ( !pAdapter->pName )
		goto Exit;

	wcscpy( pAdapter->pName, pName );
	pAdapter->Instance.NameLength = (wcslen(pName)+1) * sizeof( *pName );
	pAdapter->Instance.ByteLength = sizeof pAdapter->Instance + DWORD_MULTIPLE(pAdapter->Instance.NameLength);
	pAdapter->Instance.NameOffset = sizeof pAdapter->Instance;
	pAdapter->Instance.UniqueID   = (LONG)InstanceIndex;
	pAdapter->hDevice = CreateFileW( pFullName, GENERIC_READ, FILE_SHARE_WRITE, NULL,
	                                 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if ( pAdapter->hDevice == INVALID_HANDLE_VALUE )
   {
      Status = GetLastError();
		ASSERT( Status != ERROR_SUCCESS );
		#if DBG
	      sprintf( DebugBuffer, "CreateFile failed opening %S\n", pAdapter->pName );
	      OutputDebugString( DebugBuffer );
		#endif
      ReportEventW( hEventLog, EVENTLOG_ERROR_TYPE, 0, DXBPERF_CREATEFILE_ERROR, NULL,
                    1, 0, &pAdapter->pName, NULL );
      goto Exit;
	}

	if ( !DsOpen( pAdapter->hDevice, IWAN_DEVICE, &pAdapter->hStream ) )
	{
      Status = ERROR_DEVICE_NOT_AVAILABLE;
		#if DBG
	      sprintf( DebugBuffer, "DsOpen failed opening adapter %S\n", pAdapter->pName );
	      OutputDebugString( DebugBuffer );
		#endif
      ReportEventW( hEventLog, EVENTLOG_ERROR_TYPE, 0, DXBPERF_DSOPEN_ERROR, NULL,
                    1, 0, &pAdapter->pName, NULL );
      goto Exit;
	}

	pAdapter->nLines = 1;
	pAdapter->pLines = GlobalAlloc( GPTR, pAdapter->nLines * sizeof pAdapter->pLines[0] );
	if ( !pAdapter->pLines )
	{
		Status = ERROR_NO_SYSTEM_RESOURCES;
		goto Exit;
	}

	{
		ULONG i;
		for ( i = 0; i < pAdapter->nLines; i++ )
		{
			WCHAR Name[32];
			PCSU_INSTANCE pLine;
			ULONG Snid = 1 + i;	// Snids are 1-based integers
			wsprintfW( (PVOID)&Name, L"%s ==> Line%d", pAdapter->pName, i );
			pLine = pAdapter->pLines[i] = NewLine( Name, Snid, i, gAdapterDefinition.Type.ObjectNameTitleIndex, InstanceIndex );
			if ( !pLine )
				goto Exit;

			{
				// Simple test: Get Iwan Stats from adapter
				#define BUFFER_SIZE ( sizeof(struct wan_stioc)+sizeof(hdlcstats_t) )
				CHAR Buffer[BUFFER_SIZE];
				struct wan_stioc *pIoc = (PVOID)&Buffer;
				isdnstats_t * pStats = (PVOID)( (PCHAR)&Buffer + sizeof *pIoc );
				BOOL bSuccess;

				ZeroMemory( Buffer, BUFFER_SIZE );
				pIoc->w_type         = WAN_STATS;
				pIoc->w_stats_offset = sizeof *pIoc;
				pIoc->w_snid         = pLine->Snid;
				pIoc->w_chanid       = 1;
				bSuccess = DsIoctl( pAdapter->hDevice, pAdapter->hStream, W_GETSTATS, &Buffer, BUFFER_SIZE );
				if ( !bSuccess )
				{
					Status = ERROR_BAD_COMMAND;
					goto Exit;
				}
			}
		}
	}

	Status = ERROR_SUCCESS;

Exit:

	if ( pFullName )
		GlobalFree( pFullName );

	if ( Status != ERROR_SUCCESS )
	{
		DestroyAdapter( pAdapter );
		pAdapter = NULL;
	}

	return pAdapter;
}	


DWORD APIENTRY
OpenDxbPerformanceData(
	LPWSTR pDeviceNames )

/*++

Routine Description:

	- Open Dxb device objects.
	- Initialize data structures used to pass data back to perfmon.  

Arguments:

    List of devicenames Dxb adapter devices.

--*/
{
   HKEY hKeyDriverPerf = NULL;
   LONG Status;
   DWORD size;
   DWORD type;
   DWORD dwFirstCounter;
   DWORD dwFirstHelp;
   //
   //  Since SCREG is multi-threaded and will call this routine in
   //  order to service remote performance queries, this library
   //  must keep track of how many times it has been opened (i.e.
   //  how many threads have accessed it). the registry routines will
   //  limit access to the initialization routine to only one thread
   //  at a time so synchronization (i.e. reentrancy) should not be
   //  a problem
   //

//	DebugBreak();
   if ( gnOpens++ )
   {
   	Status = ERROR_SUCCESS;
		goto Exit;
   }

   // open Eventlog interface
   hEventLog = MonOpenEventLog();

   // get counter and help index base values from registry
   //      Open key to registry entry
   //      read First Counter and First Help values
   //      update static data strucutures by adding base to
   //          offset value in structure.

   Status = RegOpenKeyEx ( HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services\\DigiDxb\\Performance", 0L, KEY_ALL_ACCESS, &hKeyDriverPerf );
   if (Status != ERROR_SUCCESS)
   {
      REPORT_ERROR_DATA (DXBPERF_PERFORMANCE_KEY_NOT_FOUND, LOG_USER, &Status, sizeof(Status));
      goto Exit;
   }

   size = sizeof (DWORD);
   Status = RegQueryValueEx( hKeyDriverPerf, "First Counter", 0L, &type, (LPBYTE)&dwFirstCounter, &size);
   if (Status != ERROR_SUCCESS)
   {
      REPORT_ERROR_DATA (DXBPERF_FIRST_COUNTER_NOT_FOUND, LOG_USER, &Status, sizeof(Status));
      goto Exit;
   }

   size = sizeof (DWORD);
   Status = RegQueryValueEx( hKeyDriverPerf, "First Help", 0L, &type, (LPBYTE)&dwFirstHelp, &size);
   if (Status != ERROR_SUCCESS)
   {
      REPORT_ERROR_DATA (DXBPERF_FIRST_HELP_NOT_FOUND, LOG_USER, &Status, sizeof(Status));
      goto Exit;
   }

   //
   //  NOTE: the initialization program could also retrieve
   //      LastCounter and LastHelp if they wanted to do
   //      bounds checking on the new number. e.g.
   //
   //      counter->CounterNameTitleIndex += dwFirstCounter;
   //      if (counter->CounterNameTitleIndex > dwLastCounter) {
   //          LogErrorToEventLog (INDEX_OUT_OF_BOUNDS);
   //      }
   //

	// Set Adapter object counters
	gAdapterDefinition.Type.ObjectNameTitleIndex += dwFirstCounter;
	gAdapterDefinition.Type.ObjectHelpTitleIndex += dwFirstHelp;

	{
		ULONG Count;
	   PPERF_COUNTER_DEFINITION pCounterDef;
      pCounterDef = (PVOID)( (PCHAR)&gAdapterDefinition.Type + sizeof gAdapterDefinition.Type );
      for ( Count=0; Count < gAdapterDefinition.Type.NumCounters; Count++ )
      {
         pCounterDef[Count].CounterNameTitleIndex += dwFirstCounter;
         pCounterDef[Count].CounterHelpTitleIndex += dwFirstHelp;
      }
	}

   // Set the Csu object counters
   gCsuDefinition.Type.ObjectNameTitleIndex += dwFirstCounter;
   gCsuDefinition.Type.ObjectHelpTitleIndex += dwFirstHelp;

	{
		ULONG Count;
	   PPERF_COUNTER_DEFINITION pCounterDef;
      pCounterDef = (PPERF_COUNTER_DEFINITION)( (PBYTE)&gCsuDefinition.Type+sizeof gCsuDefinition.Type );
      for ( Count=0; Count < gCsuDefinition.Type.NumCounters; Count++ )
      {
         pCounterDef[Count].CounterNameTitleIndex += dwFirstCounter;
         pCounterDef[Count].CounterHelpTitleIndex += dwFirstHelp;
      }
	}

   // Set the Channel object counters
   gChannelDefinition.Type.ObjectNameTitleIndex += dwFirstCounter;
   gChannelDefinition.Type.ObjectHelpTitleIndex += dwFirstHelp;

	{
		ULONG Count;
	   PPERF_COUNTER_DEFINITION pCounterDef;
      pCounterDef = (PPERF_COUNTER_DEFINITION)( (PBYTE)&gChannelDefinition.Type+sizeof gChannelDefinition.Type );
      for ( Count=0; Count < gChannelDefinition.Type.NumCounters; Count++ )
      {
         pCounterDef[Count].CounterNameTitleIndex += dwFirstCounter;
         pCounterDef[Count].CounterHelpTitleIndex += dwFirstHelp;
      }
	}

	InitializeListHead( &gAdapterList );
	while ( pDeviceNames && *pDeviceNames )
	{
		PADAPTER_INSTANCE pAdapter = NULL;
		PWSTR pName = pDeviceNames;
		// strip off "\\Device\\" prefix, leaving the devicename
      while (*pName==L'\\') pName++;
      while (*pName!=L'\\') pName++;
      while (*pName==L'\\') pName++;
		
		pAdapter = NewAdapter( pName, gnAdapters );
		if ( !pAdapter )
		{
			Status = ERROR_NO_SYSTEM_RESOURCES;
			goto Exit;
		}

		InsertTailList( &gAdapterList, &pAdapter->ListEntry );
		gnAdapters++;
		if ( Status != ERROR_SUCCESS )
			goto Exit;
		pDeviceNames += wcslen( pDeviceNames ) + 1;
	}

   gInitialized = TRUE; // ok to use this function
   Status = ERROR_SUCCESS; // for successful exit

Exit:

	if ( hKeyDriverPerf )
		RegCloseKey( hKeyDriverPerf );
   return Status;
}


STATIC DWORD
CollectChannelData( 
	IN PVOID     pBuf,
	IN DWORD     cbBuf,
	OUT PDWORD   pcbWritten,
	OUT PDWORD   pcbNeeded )
{
	DWORD Status = ERROR_MORE_DATA;
	PDXB_CHANNEL_DEFINITION pChannelDefinition = pBuf;
	ULONG cbWritten = 0;
	ULONG cbNeeded  = 0;

	// Write Object header for the Dxb Line Object Type
	{
		cbNeeded += sizeof gChannelDefinition;
		if ( cbBuf < cbNeeded )
			goto Exit;
		*pChannelDefinition = gChannelDefinition;	// Note: structure copy!
		cbWritten += sizeof gChannelDefinition;
	}

	// Walk all Line Object Instances
	{
		PLIST_ENTRY pHead = &gAdapterList;
		PLIST_ENTRY pEntry = gAdapterList.Flink;
		while( pEntry != pHead )
		{
			PADAPTER_INSTANCE const pAdapter = CONTAINING_RECORD( pEntry, ADAPTER_INSTANCE, ListEntry );
			ULONG iLine;
			for ( iLine = 0; iLine < pAdapter->nLines; iLine++ )
			{
				PCSU_INSTANCE pLine = pAdapter->pLines[iLine];
				DXB_CHANNEL_DATA DxbTotal = {0};
				ULONG iChannel;
				
				// Write Channel Object Instances
				for ( iChannel = 0; iChannel < pLine->nChannels; iChannel++ )
				{
					PCHANNEL_INSTANCE pChannel = pLine->pChannels[iChannel];
					DXB_CHANNEL_DATA DxbData = {0};
					PERF_COUNTER_BLOCK Counter = {0};

					{
						#define BUFFER_SIZE ( sizeof(struct wan_stioc)+sizeof(hdlcstats_t) )
						CHAR Buffer[BUFFER_SIZE];
						struct wan_stioc *pIoc = (PVOID)&Buffer;
						isdnstats_t * pStats = (PVOID)( (PCHAR)&Buffer + sizeof *pIoc );
						BOOL bSuccess;

						// Get Stats from adapter

						ZeroMemory( Buffer, BUFFER_SIZE );
						pIoc->w_type         = WAN_STATS;
						pIoc->w_stats_offset = sizeof *pIoc;
						pIoc->w_snid         = pLine->Snid;
						pIoc->w_chanid       = iChannel + 1;
						bSuccess = DsIoctl( pAdapter->hDevice, pAdapter->hStream, W_GETSTATS, &Buffer, BUFFER_SIZE );
						if ( !bSuccess )
						{
							Status = ERROR_SUCCESS;
							goto Exit;
						}

						// Fill in counters for PerfMon
						DxbTotal.TxOctets += DxbData.TxOctets = pStats->hc_txoctets;
						DxbTotal.TxGood   += DxbData.TxGood   = pStats->hc_txgood;
						DxbTotal.TxClass1 += DxbData.TxClass1 = pStats->hc_txclass1;
						DxbTotal.TxClass2 += DxbData.TxClass2 = pStats->hc_txclass2;
						DxbTotal.TxDef    += DxbData.TxDef    = pStats->hc_txdef;
						DxbTotal.TxUrun   += DxbData.TxUrun   = pStats->hc_txurun;
						DxbTotal.RxOctets += DxbData.RxOctets = pStats->hc_rxoctets;
						DxbTotal.RxGood   += DxbData.RxGood   = pStats->hc_rxgood;
						DxbTotal.RxErr    += DxbData.RxErr    = pStats->hc_rxerr;
						DxbTotal.RxOrun   += DxbData.RxOrun   = pStats->hc_rxorun;
						DxbTotal.RxCrc    += DxbData.RxCrc    = pStats->hc_rxcrc;
						DxbTotal.RxNoBuf  += DxbData.RxNoBuf  = pStats->hc_rxnobuf;
						DxbTotal.RxNflow  += DxbData.RxNflow  = pStats->hc_rxnflow;
						DxbTotal.RxOflow  += DxbData.RxOflow  = pStats->hc_rxoflow;
						DxbTotal.RxAbort  += DxbData.RxAbort  = pStats->hc_rxabort;
					}

					// Write this channel instance
					cbNeeded += sizeof pChannel->Instance + pChannel->Instance.NameLength + sizeof Counter + sizeof DxbData;
					if ( cbBuf < cbNeeded )
						goto Exit;

					// Write constant Instance definition
					CopyMemory( (PCHAR)pBuf+cbWritten, &pChannel->Instance, sizeof pChannel->Instance );
					cbWritten += sizeof pChannel->Instance;

					// Write constant Instance name
					CopyMemory( (PCHAR)pBuf+cbWritten, pChannel->pName, pChannel->Instance.NameLength );
					cbWritten += DWORD_MULTIPLE( pChannel->Instance.NameLength );

					// Write Counter Block 
					Counter.ByteLength = sizeof Counter + sizeof DxbData;
					CopyMemory( (PCHAR)pBuf+cbWritten, &Counter, sizeof Counter );
					cbWritten += sizeof Counter;

					// Write Instance data
					CopyMemory( (PCHAR)pBuf+cbWritten, &DxbData, sizeof DxbData );
					cbWritten += sizeof DxbData;

					pChannelDefinition->Type.NumInstances++;
				}

				// Write the Total instance
				{
					PERF_COUNTER_BLOCK Counter = {0};
					PCHANNEL_INSTANCE pChannel = pLine->pTotalChannel;
					cbNeeded += sizeof pChannel->Instance + pChannel->Instance.NameLength + sizeof Counter + sizeof DxbTotal;
					if ( cbBuf < cbNeeded )
						goto Exit;

					// Write constant Instance definition
					CopyMemory( (PCHAR)pBuf+cbWritten, &pChannel->Instance, sizeof pChannel->Instance );
					cbWritten += sizeof pChannel->Instance;

					// Write constant Instance name
					CopyMemory( (PCHAR)pBuf+cbWritten, pChannel->pName, pChannel->Instance.NameLength );
					cbWritten += DWORD_MULTIPLE( pChannel->Instance.NameLength );

					// Write Counter Block 
					Counter.ByteLength = sizeof Counter + sizeof DxbTotal;
					CopyMemory( (PCHAR)pBuf+cbWritten, &Counter, sizeof Counter );
					cbWritten += sizeof Counter;

					// Write Instance data
					CopyMemory( (PCHAR)pBuf+cbWritten, &DxbTotal, sizeof DxbTotal );
					cbWritten += sizeof DxbTotal;

					pChannelDefinition->Type.NumInstances++;
				}
			}

			pEntry = pEntry->Flink;
		}
	}

	ASSERT( DWORD_MULTIPLE( cbWritten ) == cbWritten );
	pChannelDefinition->Type.TotalByteLength = cbWritten;
	Status = ERROR_SUCCESS;

Exit:

	*pcbWritten = cbWritten;
	*pcbNeeded = cbNeeded;

	return Status;
}


STATIC DWORD
CollectLineData( 
	IN PVOID     pBuf,
	IN DWORD     cbBuf,
	OUT PDWORD   pcbWritten,
	OUT PDWORD   pcbNeeded )
{
	DWORD Status = ERROR_MORE_DATA;
	PDXB_CSU_DEFINITION pCsuDefinition = pBuf;
	ULONG cbWritten = 0;
	ULONG cbNeeded  = 0;

	// Write Object header for the Dxb Line Object Type
	{
		cbNeeded += sizeof gCsuDefinition;
		if ( cbBuf < cbNeeded )
			goto Exit;
		*pCsuDefinition = gCsuDefinition;	// Note: structure copy!
		cbWritten += sizeof gCsuDefinition;
	}

	// Write Line Object Instances
	{
		PLIST_ENTRY pHead = &gAdapterList;
		PLIST_ENTRY pEntry = gAdapterList.Flink;
		while( pEntry != pHead )
		{
			PADAPTER_INSTANCE const pAdapter = CONTAINING_RECORD( pEntry, ADAPTER_INSTANCE, ListEntry );
			ULONG i;
			for ( i = 0; i < pAdapter->nLines; i++ )
			{
				PCSU_INSTANCE pLine = pAdapter->pLines[i];
				DXB_CSU_DATA DxbData = {0};
				PERF_COUNTER_BLOCK Counter = {0};

				// TODO: Get actual Csu stats here

				cbNeeded += sizeof pLine->Instance + pLine->Instance.NameLength + sizeof Counter + sizeof DxbData;
				if ( cbBuf < cbNeeded )
					goto Exit;

				// Write constant Instance definition
				CopyMemory( (PCHAR)pBuf+cbWritten, &pLine->Instance, sizeof pLine->Instance );
				cbWritten += sizeof pLine->Instance;

				// Write constant Instance name
				CopyMemory( (PCHAR)pBuf+cbWritten, pLine->pName, pLine->Instance.NameLength );
				cbWritten += DWORD_MULTIPLE( pLine->Instance.NameLength );

				// Write Counter Block 
				Counter.ByteLength = sizeof Counter + sizeof DxbData;
				CopyMemory( (PCHAR)pBuf+cbWritten, &Counter, sizeof Counter );
				cbWritten += sizeof Counter;

				// Write Instance data
				CopyMemory( (PCHAR)pBuf+cbWritten, &DxbData, sizeof DxbData );
				cbWritten += sizeof DxbData;
			}

			pCsuDefinition->Type.NumInstances++;
			pEntry = pEntry->Flink;
		}
	}

	ASSERT( DWORD_MULTIPLE( cbWritten ) == cbWritten );
	pCsuDefinition->Type.TotalByteLength = cbWritten;
	Status = ERROR_SUCCESS;

Exit:

	*pcbWritten = cbWritten;
	*pcbNeeded = cbNeeded;

	return Status;
}


STATIC DWORD
CollectAdapterData( 
	IN PVOID     pBuf,
	IN DWORD     cbBuf,
	OUT PDWORD   pcbWritten,
	OUT PDWORD   pcbNeeded )
{
	DWORD Status = ERROR_MORE_DATA;
	PDXB_ADAPTER_DEFINITION pAdapterDefinition = pBuf;
	ULONG cbWritten = 0;
	ULONG cbNeeded  = 0;

	// Write Object header for the Dxb Adapter Object Type
	{
		cbNeeded += sizeof gAdapterDefinition;
		if ( cbBuf < cbNeeded )
			goto Exit;
		*pAdapterDefinition = gAdapterDefinition;	// Note: structure copy!
		cbWritten += sizeof gAdapterDefinition;
	}

	// Write Adapter Object Instances
	{
		PLIST_ENTRY pHead = &gAdapterList;
		PLIST_ENTRY pEntry = gAdapterList.Flink;
		while( pEntry != pHead )
		{
			DXB_ADAPTER_DATA DxbData = {0};
			PADAPTER_INSTANCE const pAdapter = CONTAINING_RECORD( pEntry, ADAPTER_INSTANCE, ListEntry );
			PERF_COUNTER_BLOCK Counter = {0};

			// TODO: Get actual Adapter stats here

			cbNeeded += sizeof pAdapter->Instance + DWORD_MULTIPLE( pAdapter->Instance.NameLength ) + sizeof Counter + sizeof DxbData;
			if ( cbBuf < cbNeeded )
				goto Exit;

			// Write constant Instance definition
			CopyMemory( (PCHAR)pBuf+cbWritten, &pAdapter->Instance, sizeof pAdapter->Instance );
			cbWritten += sizeof pAdapter->Instance;

			// Write constant Instance name
			CopyMemory( (PCHAR)pBuf+cbWritten, pAdapter->pName, pAdapter->Instance.NameLength );
			cbWritten += DWORD_MULTIPLE( pAdapter->Instance.NameLength );

			// Write Counter Block 
			Counter.ByteLength = sizeof Counter + sizeof DxbData;
			CopyMemory( (PCHAR)pBuf+cbWritten, &Counter, sizeof Counter );
			cbWritten += sizeof Counter;

			// Write Instance data
			CopyMemory( (PCHAR)pBuf+cbWritten, &DxbData, sizeof DxbData );
			cbWritten += sizeof DxbData;

			pAdapterDefinition->Type.NumInstances++;
			pEntry = pEntry->Flink;
		}
	}

	ASSERT( DWORD_MULTIPLE(cbWritten) == cbWritten );
	pAdapterDefinition->Type.TotalByteLength = cbWritten;
	Status = ERROR_SUCCESS;

Exit:

	*pcbWritten = cbWritten;
	*pcbNeeded = cbNeeded;

	return Status;
}


DWORD APIENTRY 
CollectDxbPerformanceData(
   IN      LPWSTR  lpValueName,
   IN OUT  LPVOID  *lppData,
   IN OUT  LPDWORD lpcbTotalBytes,
   IN OUT  LPDWORD lpNumObjectTypes )
/*++

Routine Description:

    This routine will return the data for the Dxb counters.

Arguments:

   lpValueName
         pointer to a wide character string passed by registry.

   *lppData
         IN: pointer to the address of the buffer to receive the completed
            PerfDataBlock and subordinate structures. This routine will
            append its data to the buffer starting at the point referenced
            by *lppData.
         OUT: points to the first byte after the data structure added by this
            routine. This routine updated the value at lppdata after appending
            its data.

   lpcbTotalBytes
         IN: the address of the DWORD that tells the size in bytes of the
            buffer referenced by the lppData argument
         OUT: the number of bytes added by this routine is writted to the
            DWORD pointed to by this argument

   NumObjectTypes
         IN: the address of the DWORD to receive the number of objects added
            by this routine
         OUT: the number of objects added by this routine is writted to the
            DWORD pointed to by this argument

Return Value:

   ERROR_MORE_DATA if buffer passed is too small to hold data
      any error conditions encountered are reported to the event log if
      event logging is enabled.

   ERROR_SUCCESS  if success or any other error. Errors, however are
      also reported to the event log.

--*/
{
   //  Variables for reformating the data
	DWORD Status = ERROR_SUCCESS;
   LPVOID lpData = *lppData;
	ULONG cbWrittenTotal = 0;
	ULONG cbNeededTotal = 0;
	ULONG cbRemaining = *lpcbTotalBytes;
	PVOID pBuf = *lppData;
   DWORD dwQueryType = GetQueryType( lpValueName );

   *lpNumObjectTypes = 0;

   if ( !gInitialized || !gnAdapters )
      goto Exit;

   if ( dwQueryType == QUERY_FOREIGN )
      goto Exit;

#if 0
	if ( dwQueryType == QUERY_GLOBAL || 
	     dwQueryType == QUERY_ITEMS && IsNumberInUnicodeList( gAdapterDefinition.Type.ObjectNameTitleIndex, lpValueName) )
	{
		ULONG cbWritten = 0;
		ULONG cbNeeded  = 0;
		Status = CollectAdapterData( pBuf, cbRemaining, &cbWritten, &cbNeeded );
		if ( cbWritten )
			*lpNumObjectTypes += 1;
		if ( Status != ERROR_SUCCESS || cbNeeded > cbWritten )
			goto Exit;

		cbWrittenTotal += cbWritten;
		pBuf = (PCHAR)pBuf + cbWritten;
		cbRemaining -= cbWritten;
		cbNeededTotal += cbNeeded;
	}

	if ( dwQueryType == QUERY_GLOBAL || 
	     dwQueryType == QUERY_ITEMS && IsNumberInUnicodeList( gCsuDefinition.Type.ObjectNameTitleIndex, lpValueName ) )
	{
		ULONG cbWritten = 0;
		ULONG cbNeeded  = 0;
		Status = CollectLineData( pBuf, cbRemaining, &cbWritten, &cbNeeded );
		if ( cbWritten )
			*lpNumObjectTypes += 1;
		if ( Status != ERROR_SUCCESS || cbNeeded > cbWritten )
			goto Exit;

		cbWrittenTotal += cbWritten;
		pBuf = (PCHAR)pBuf + cbWritten;
		cbRemaining -= cbWritten;
		cbNeededTotal += cbNeeded;
	}
#endif

	if ( dwQueryType == QUERY_GLOBAL || 
	     dwQueryType == QUERY_ITEMS && IsNumberInUnicodeList( gChannelDefinition.Type.ObjectNameTitleIndex, lpValueName ) )
	{
		ULONG cbWritten = 0;
		ULONG cbNeeded  = 0;
		Status = CollectChannelData( pBuf, cbRemaining, &cbWritten, &cbNeeded );
		if ( cbWritten )
			*lpNumObjectTypes += 1;
		if ( Status != ERROR_SUCCESS || cbNeeded > cbWritten )
			goto Exit;

		cbWrittenTotal += cbWritten;
		pBuf = (PCHAR)pBuf + cbWritten;
		cbRemaining -= cbWritten;
		cbNeededTotal += cbNeeded;
	}

	ASSERT( (ULONG)pBuf - (ULONG)lpData  == cbWrittenTotal );
	lpData = pBuf;

Exit:

   *lpcbTotalBytes = (ULONG)(lpData) - (ULONG)(*lppData);

#ifdef VERY_LOUD
	DebugWalkData( *lppData, *lpcbTotalBytes, *lpNumObjectTypes );
#endif
   *lppData = lpData;

   return Status;
}

DWORD APIENTRY
CloseDxbPerformanceData()

/*++

Routine Description:

    This routine closes the open handles to FEP device performance counters

Arguments:

    None.


Return Value:

    ERROR_SUCCESS

--*/

{
   if (!(--gnOpens))
   { // when this is the last thread...

      while (!IsListEmpty( &gAdapterList ) )
      {
			PADAPTER_INSTANCE pAdapter;
			PLIST_ENTRY pEntry = RemoveHeadList( &gAdapterList );
			pAdapter = CONTAINING_RECORD( pEntry, ADAPTER_INSTANCE, ListEntry );
			DestroyAdapter( pAdapter );
      }

      MonCloseEventLog();
   }
   return ERROR_SUCCESS;

}

