/*
 ****************************************************************************
 * Copyright (c) 1999-2000 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 File:  asyninit.c
 *
 * Author      : Sandy Haapala	  
 *
 * Purpose     :
 *
 *    Initialize the DigiAsyn driver
 *
 *----------------------------------------------------------------------
 */

#include "digiasyn.h"
#include "digidga.h"

STATIC VOID
FreePortNames(
	IN PASYN_PORT_EXT		pPortExt
)
{
	// Undo the external port naming    
	{   
		// Delete the DeviceMap entry
		UNICODE_STRING DeviceMapName = { 0 };

		// Convert port number to a string
		UNICODE_STRING SdxbNumber;
		WCHAR buf[32];
		SdxbNumber.Buffer = buf;
		SdxbNumber.Length = 0;
		SdxbNumber.MaximumLength = sizeof buf;
		if ( NT_SUCCESS( RtlIntegerToUnicodeString( pPortExt->iSdxbPort, 10, &SdxbNumber )))
		{
#if NT5_BUILD
			if ( NT_SUCCESS( DigiStrAllocUnicodeString( &DeviceMapName, pPortExt->DxbDevice.Buffer,DXB_HDWID_ASYN, SdxbNumber.Buffer ) ) )
#else         
			if ( NT_SUCCESS( DigiStrAllocUnicodeString( &DeviceMapName, pPortExt->pParentExt->InstanceName.Buffer, DXB_HDWID_ASYN, SdxbNumber.Buffer ) ) )
#endif         
			{
				(void) RtlDeleteRegistryValue( RTL_REGISTRY_DEVICEMAP, L"SERIALCOMM", DeviceMapName.Buffer );
				DigiStrFreeUnicodeString( &DeviceMapName );
			}
		}         
	}            

	// Delete the symbolic link from the NT name space to the DosDevices name space.
	{
		UNICODE_STRING SymbolicName = { 0 };

		if ( NT_SUCCESS( DigiStrSymbolicName( &SymbolicName, pPortExt->PortName.Buffer ) ) )
		{
			(void) IoDeleteSymbolicLink( &SymbolicName );
			DigiStrFreeUnicodeString( &SymbolicName );
		}
	}
}

VOID AsynFreePortResources( 
	IN PASYN_PORT_EXT pPortExt 
)
{	
	DigiDump( DIGIUNLOAD , ("DigiAsyn!AsynFreePortResources\n"));

	FreePortNames( pPortExt );
	DigiAcquireSpinLock( &pPortExt->Lock );
	pPortExt->State = ShutdownState;
	DigiReleaseSpinLock(&pPortExt->Lock );

	if ( pPortExt->DeviceName.Buffer )
		DigiStrFreeUnicodeString( &pPortExt->DeviceName );
	if ( pPortExt->PortName.Buffer )
		DigiStrFreeUnicodeString( &pPortExt->PortName );      
#if NT5_BUILD      
	if ( pPortExt->DxbDevice.Buffer )
		DigiStrFreeUnicodeString( &pPortExt->DxbDevice );      
#endif      
      
      
}

STATIC NTSTATUS
DestructAdapter(
	IN PASYN_ADAPTER_EXT		pExt )
/*++

Description:
   
Parameters:

Return Value:


--*/
{
	DigiDump( DIGIINIT, ( "DestructAdapter PortList=%p\n",pExt->PortList ));
	// Walk port list
	while( !IsListEmpty( &pExt->PortList ))
	{
		PASYN_PORT_EXT pPortExt;
		PLIST_ENTRY pEntry = RemoveHeadList( &pExt->PortList );
		pPortExt = CONTAINING_RECORD( pEntry, ASYN_PORT_EXT, ListEntry );
		ASSERT( pPortExt->Type == ASYN_TYPE_PORT_EXT );
		AsynFreePortResources( pPortExt );
		IoDeleteDevice( pPortExt->pDevice );
	} 
   
	if ( pExt->DsHandle != NULL )
		DsCloseDevice( pExt->DsHandle );         
         
	{
		UNICODE_STRING Buf = {0};
      
		// Delete symbolic link
		if ( NT_SUCCESS( DigiStrAllocUnicodeString( &Buf, DXB_HDWID_ASYN, pExt->InstanceName.Buffer, NULL ) ) )
		{
			(void) IoDeleteSymbolicLink( &Buf );
			DigiStrFreeUnicodeString( &Buf );
		}       

		DigiStrFreeUnicodeString( &pExt->InstanceName );

		// Delete device object
		IoDeleteDevice( pExt->pDevice );
	}         

	return STATUS_SUCCESS;
}

VOID
AsynDestructAllPorts()
{
	DigiDump( DIGIINIT, ( "ASYN!AsynDestructAllPorts\n") );

	// Walk adapter list 
	while ( !IsListEmpty( &gDeviceExtList ))
	{
		PASYN_ADAPTER_EXT pExt;
		PLIST_ENTRY pEntry;

		DigiDump( DIGIINIT, ( "ASYN!AsynDestructAllPorts gDeviceExtList is not empty\n") );

		pEntry = RemoveHeadList( &gDeviceExtList );

		DigiDump( DIGIINIT, ( "ASYN!AsynDestructAllPorts pEntry = %p\n",pEntry) );

		pExt = CONTAINING_RECORD( pEntry, ASYN_ADAPTER_EXT, ListEntry );
		DigiDump( DIGIINIT, ( "ASYN!AsynDestructAllPorts pExt = %p\n",pExt) );

		DestructAdapter( pExt );
	}
}

STATIC NTSTATUS
ReadPortRegistrySettings(
	IN	PASYN_PORT_EXT		pPortExt, 
	IN HANDLE				hPortKey   
)

{
#define MAX_CWC_PORT_NAME 20
#define COM_CHARS_STRIP 3
   ULONG       nAdptrComNumber;
	NTSTATUS    Status = STATUS_SUCCESS;
	ULONG       DefCloseTimeout = DXB_REGVAL_ASYN_CLOSETIMEOUT_DEFAULT;
	ULONG       DefCompleteWhenSent = DXB_REGVAL_ASYN_COMPLETEWHENSENT_DEFAULT; 
   UNICODE_STRING strTempString = {0};

   // Read the Port's values registry settings
	(void)DigiRegQueryDword( hPortKey, DXB_REGVAL_ASYN_CLOSETIMEOUT, &pPortExt->Close.TxDrainTimeout, DigiRegFlagOptional, DXB_REGVAL_ASYN_CLOSETIMEOUT_DEFAULT);      
	DigiDump( DIGIINIT, ("TxDrainTimeout = %d\n",pPortExt->Close.TxDrainTimeout ));
	(void)DigiRegQueryDword( hPortKey, DXB_REGVAL_SERIAL_ALT_PIN, &pPortExt->AltPin, DigiRegFlagOptional, DXB_REGVAL_SERIAL_ALT_PIN_DEFAULT);
	(void)DigiRegQueryDword( hPortKey, DXB_REGVAL_ASYN_COMPLETEWHENSENT, &pPortExt->Write.Sequence.WaitTillDone, DigiRegFlagOptional, DXB_REGVAL_ASYN_COMPLETEWHENSENT_DEFAULT );
	(void)DigiRegQueryDword( hPortKey, DXB_REGVAL_ASYN_MODEMDEVICE, &pPortExt->IsModem, DigiRegFlagOptional, DXB_REGVAL_ASYN_MODEMDEVICE_DEFAULT );
   (void)DigiRegQueryDword( hPortKey, DXB_REGVAL_ASYN_LOWLATENCY, &pPortExt->fLowLatency, DigiRegFlagOptional, DXB_REGVAL_ASYN_LOWLATENCY_DEFAULT );
	DigiDump( DIGIINIT, ("LowLatency = %x\n",pPortExt->fLowLatency ));
#if !NT5_BUILD
	Status = DigiRegQuerySz( hPortKey, DXB_REGVAL_ASYN_DOSDEVICES, &strTempString, DigiRegFlagRequired, NULL );
	if (NT_ERROR( Status ))	goto Exit;
   if (NULL != pPortExt->PortName.Buffer)
      DigiStrFreeUnicodeString( &pPortExt->PortName );
   DigiStrAllocUnicodeString(&pPortExt->PortName, strTempString.Buffer, NULL, NULL);
   DigiRegFreeString( &strTempString );
#else

   Status = DigiRegQueryDword( hPortKey, DXB_REGVAL_ASYN_SDXB_PORT, &pPortExt->iSdxbPort, DigiRegFlagRequired, 0 );

   // A bunch of fooling around so we get Non-paged mem alloc'd for this string in the dev ext
   Status = DigiRegQuerySz( hPortKey, DXB_REGVAL_DEVICE, &strTempString, DigiRegFlagRequired, NULL );
	if (NT_ERROR( Status ))	goto Exit;
   if (NULL != pPortExt->DxbDevice.Buffer)
      DigiStrFreeUnicodeString( &pPortExt->DxbDevice );
   DigiStrAllocUnicodeString(&pPortExt->DxbDevice, strTempString.Buffer, NULL, NULL);
   DigiRegFreeString( &strTempString );

      
	if (NT_SUCCESS( Status ))
   {
   	ULONG				nEnumComNumber;
#if defined REMOVED_CONFIG_WRITES_THIS_FOR_US
	   UNICODE_STRING	PortNameKey;
#endif
      UNICODE_STRING strEnumPortName = {0};
      UNICODE_STRING strEnumComNumber = {0};

      UNICODE_STRING strNewEnumComNumber = {0};
      UNICODE_STRING strAdapterComNumber = {0};
      WCHAR          PortNameBuf[MAX_CWC_PORT_NAME] = {0};
   	HANDLE hKeyDriver;

      (void)IoOpenDeviceRegistryKey( pPortExt->pPdo, PLUGPLAY_REGKEY_DEVICE, MAXIMUM_ALLOWED, &hKeyDriver );
      Status = DigiRegQuerySz( hKeyDriver, DXB_REGVAL_ASYN_PORTNAME, &strEnumPortName, DigiRegFlagRequired, NULL );
		if (NT_ERROR(Status)) goto Exit;
      RtlInitUnicodeString( &strEnumComNumber, &strEnumPortName.Buffer[COM_CHARS_STRIP]);
      RtlUnicodeStringToInteger(	&strEnumComNumber, 10, &nEnumComNumber);

      // Get the adapter instance's idea of the COM number, for the first config, it will be zero
	   (void)DigiRegQueryDword( hPortKey, DXB_REGVAL_COM_NUMBER, &nAdptrComNumber, DigiRegFlagOptional, DXB_REGVAL_COM_NUMBER_DEFAULT);

#if defined REMOVED_CONFIG_WRITES_THIS_FOR_US
// For now, the config is writing the PortName value on our behalf
// If the async driver should ever need to write it, use this code, it works
      if (nAdptrComNumber != nEnumComNumber)
      {
         DigiDump( DIGIINIT, ("ComNumbers differ\n"));
         if (0 == nAdptrComNumber)
         {
            // This is the first time we've loaded, copy the port's number to the adapter
   			Status = DigiRegWriteDword( hPortKey, DXB_REGVAL_COM_NUMBER, nEnumComNumber );
         	DigiDump( DIGIINIT, ("Updating the adapter's key with the ComNumber:%d\n",nEnumComNumber ));
            // Extension gets the same name we got from enum
            if (NULL != pPortExt->PortName.Buffer)
               DigiStrFreeUnicodeString( &pPortExt->PortName );
            DigiStrAllocUnicodeString(&pPortExt->PortName, strEnumPortName.Buffer, NULL, NULL);
         }
         else
         {
            // Get a string rep of the integer COM number from the adapter
            RtlIntegerToUnicodeString( nAdptrComNumber, 10, &strNewEnumComNumber);
            if (NULL != pPortExt->PortName.Buffer)
               DigiStrFreeUnicodeString( &pPortExt->PortName );
            DigiStrAllocUnicodeString(&pPortExt->PortName, L"COM", strNewEnumComNumber.Buffer, NULL);
            Status = ZwSetValueKey(hKeyDriver, &PortNameKey, 0, DXB_REGVAL_ASYN_PORTNAME_TYPE, pPortExt->PortName.Buffer, pPortExt->PortName.Length + sizeof(WCHAR));
            DigiDump( DIGIINIT, ("Updated the port's key with the PortName:%wZ\n",&pPortExt->PortName ));
         }
      }
      else
      {
         DigiDump( DIGIINIT, ("ComNumbers are the same\n"));
         // Just write the string we got from the enum key into our extension
         if (NULL != pPortExt->PortName.Buffer)
            DigiStrFreeUnicodeString( &pPortExt->PortName );
         DigiStrAllocUnicodeString(&pPortExt->PortName, strEnumPortName.Buffer, NULL, NULL);
      }
#else // #if defined USE_ADAPTER_COM_NUMBER_IF_NON_ZERO
      // The config writes the friendly name whenever there is a change for the driver
      // The config only needs to know what the original value is (indicated when adapter's com number is 0)
      if (0 == nAdptrComNumber)
      {
         // This is the first time we've loaded, copy the port's number to the adapter
   		Status = DigiRegWriteDword( hPortKey, DXB_REGVAL_COM_NUMBER, nEnumComNumber );
         DigiDump( DIGIINIT, ("Updating the adapter's key with the ComNumber:%d\n",nEnumComNumber ));
      }
      // Extension gets the same name we got from enum
      if (NULL != pPortExt->PortName.Buffer)
         DigiStrFreeUnicodeString( &pPortExt->PortName );
      DigiStrAllocUnicodeString(&pPortExt->PortName, strEnumPortName.Buffer, NULL, NULL);
#endif // #if defined USE_ADAPTER_COM_NUMBER_IF_NON_ZERO

      DigiRegFreeString( &strEnumPortName );

		ZwClose( hKeyDriver );
   }
#endif
Exit:
	if ( NT_ERROR( Status ) )
		DigiDump( DIGIINIT, ("ReadPortRegistrySettings: Status=%x\n",Status));
	return Status;  
}      


STATIC NTSTATUS
InitializePortSettings(
	IN PASYN_PORT_EXT		pPortExt,
	IN HANDLE				hPortKey
)
/*++

Description

	Initialize the default port settings
   
--*/
{
	NTSTATUS Status = STATUS_SUCCESS;
   
	DigiInitializeSpinLock( &pPortExt->Lock );
   
	pPortExt->pDevice->Flags |= DO_BUFFERED_IO;
	pPortExt->State = IdleState;

	{   
		PASYN_SDXB	pSdxbExt = &pPortExt->SdxbValues;
   
		DigiInitializeSpinLock( &pSdxbExt->Lock );
		pSdxbExt->PortParams.speed = ASYN_DEF_BAUDRATE;
   
		// Default line control protocol, is 7E1
		pSdxbExt->PortParams.cflags = SDXB_CREAD | SDXB_CS7 | SDXB_PARENB;
		pSdxbExt->PortParams.iflags = SDXB_INPCK;
		pSdxbExt->PortParams.maxlen = SDXB_MAXLEN_DEF;
      
		// Set altpin based on registry setting.
		if ( pPortExt->AltPin )
			pSdxbExt->PortParams.xflags = SDXB_XALTPIN;
		else
			pSdxbExt->PortParams.xflags = 0;         
      
      
		// Default Modem signals are: DTR enabled and RTS/CTS flow control
		pSdxbExt->Mout = SDXB_DTR;
		pSdxbExt->PortParams.mflow = SDXB_RTS;
										  
		pSdxbExt->PortParams.xon = ASYN_DEF_XON;
		pSdxbExt->PortParams.xoff = ASYN_DEF_XOFF;

		if ( pPortExt->fLowLatency )
		{      
			DigiDump( DIGIINIT, ("Setting low latency SDXB parameters\n" ));
			// Establish the low latency receive/transmit parameters
			pPortExt->SdxbValues.Rmax = SDXB_RMAX_LOWLATENCY;
			pPortExt->SdxbValues.Rtime = SDXB_RTIME_LOWLATENCY;
	      pPortExt->SdxbValues.Icdtime = SDXB_ICDTIME_LOWLATENCY;
			pPortExt->SdxbValues.Tmax = SDXB_TMAX_LOWLATENCY;
			pPortExt->SdxbValues.Ttime = SDXB_TTIME_LOWLATENCY;
		}
      else
		{      
			// Establish the polling receive/transmit parameters
			DigiDump( DIGIINIT, ("Setting Default SDXB parameters\n" ));
			pPortExt->SdxbValues.Rmax = SDXB_RMAX_DEF;
			pPortExt->SdxbValues.Rtime = SDXB_RTIME_DEF;
	      pPortExt->SdxbValues.Icdtime = SDXB_ICDTIME_DEF;
			pPortExt->SdxbValues.Tmax = SDXB_TMAX_DEF;
			pPortExt->SdxbValues.Ttime = SDXB_TTIME_DEF;
		}
               
	
		pPortExt->SdxbValues.Ltime = SDXB_LTIME_DEF;

		InitializeListHead( &pSdxbExt->PendingIrps );
	}	
   
    KeInitializeDpc( &pPortExt->Close.DPC, AsynCloseTimedOut, pPortExt );
    KeInitializeTimer( &pPortExt->Close.Timer);
   
    InitializeListHead( &pPortExt->Trace.TraceQueue );
	DigiInitializeSpinLock( &pPortExt->Trace.TraceLock );

	KeInitializeTimer( &pPortExt->Trace.TraceTimer );
	KeInitializeDpc( &pPortExt->Trace.TraceDpc, AsynTraceTimeout, pPortExt );
	pPortExt->Trace.Timeout.QuadPart = 500 * -1 * MILLISECOND;

    Status = AsynReadInit( pPortExt );

	if ( NT_SUCCESS( Status ) )
		Status = AsynWriteInitialize( pPortExt, hPortKey  );
   
	DigiInitializeSpinLock( &pPortExt->Wait.Lock );
   
	DigiDump( DIGIINIT, ( "InitializePortSettings: exiting with Status 0x%x\n", Status ) );
   
    return Status;
}


STATIC NTSTATUS 
InitializePortNames(
	IN PASYN_PORT_EXT		pPortExt
)
/*++

Description

   
--*/
{
	NTSTATUS Status = STATUS_SUCCESS;
	DigiDump( DIGIINIT, ("InitializePortNames\n"));

	ASSERT( pPortExt->PortName.Buffer );
   ASSERT( pPortExt->DeviceName.Buffer );   
   
	// Create the symbolic link from the NT name space to the DosDevices name space.
	{
		UNICODE_STRING SymbolicName = { 0 };
      
		Status = DigiStrSymbolicName( &SymbolicName, pPortExt->PortName.Buffer );

		if ( NT_SUCCESS( Status ) )
		{
			Status = IoCreateSymbolicLink( &SymbolicName, &pPortExt->DeviceName );
			DigiStrFreeUnicodeString( &SymbolicName );
		}
	}
   
	if ( NT_ERROR( Status ) )
	{
      AsynLogNoSymLink( Status, &pPortExt->PortName );
      goto Exit;
	}      

	// Add DeviceMap Registry entry under SERIALCOMM Registry key.
	{
		UNICODE_STRING DeviceMapName = { 0 };
      
		{   
			UNICODE_STRING SdxbNumber;
			WCHAR buf[32];
			SdxbNumber.Buffer = buf;
			SdxbNumber.Length = 0;
			SdxbNumber.MaximumLength = sizeof buf;
			Status = RtlIntegerToUnicodeString( pPortExt->iSdxbPort, 10, &SdxbNumber );
#if NT5_BUILD
			if ( NT_SUCCESS( DigiStrAllocUnicodeString( &DeviceMapName, pPortExt->DxbDevice.Buffer,DXB_HDWID_ASYN, SdxbNumber.Buffer ) ) )
#else         
			if ( NT_SUCCESS( DigiStrAllocUnicodeString( &DeviceMapName, pPortExt->pParentExt->InstanceName.Buffer,DXB_HDWID_ASYN, SdxbNumber.Buffer ) ) )
#endif         
			{
#if TEST         
// TODO
				(void) RtlWriteRegistryValue( RTL_REGISTRY_DEVICEMAP, L"SERIALCOMM", DeviceMapName.Buffer,
				                            REG_SZ,	pPortExt->PortName.Buffer, pPortExt->PortName.Length+sizeof(WCHAR));
#else                                        
				(void) RtlWriteRegistryValue( RTL_REGISTRY_DEVICEMAP, L"SERIALCOMM", pPortExt->DeviceName.Buffer,
				                            REG_SZ,	pPortExt->PortName.Buffer, pPortExt->PortName.Length+sizeof(WCHAR));
#endif                                        
				DigiDump( DIGIINIT, ("DigiAsyn!InitPortNames: Adding SERIALCOMM device = %wZ SymbolicName = %S\n", &DeviceMapName,pPortExt->PortName.Buffer));
				DigiStrFreeUnicodeString( &DeviceMapName );
			}
			else
			{
				AsynLogNoDeviceMap( &pPortExt->PortName );
			}            
		}
   }
   
Exit:   
	DigiDump( DIGIINIT, ( "InitializePortNames: exiting with Status 0x%x\n", Status ) );
	return Status;
}


NTSTATUS
AsynInitPort(
	IN	PASYN_PORT_EXT	pPortExt,
	IN HANDLE	hPortKey
)
{
	NTSTATUS Status = STATUS_SUCCESS;

	// Get Registry settings
	Status = ReadPortRegistrySettings( pPortExt, hPortKey );
	if ( NT_ERROR( Status ) )
		goto Exit;
	   
	// Initialize device extension
	Status = InitializePortSettings( pPortExt, hPortKey );
	if ( NT_ERROR( Status ) )
		goto Exit;   
	   
	// Initialize external port naming      
	Status = InitializePortNames( pPortExt );

Exit:
	if ( NT_ERROR( Status ) )
		AsynFreePortResources( pPortExt );      
	      
	DigiDump( DIGIINIT, ( "AsynInitPort: exiting with Status 0x%x\n", Status ) );
	return Status;   
}


STATIC NTSTATUS
FindPortsOnAdapter(
	IN HANDLE						hKeyServices,
	IN PUNICODE_STRING			pName,
	IN PUNICODE_STRING			pRegPath,				
	IN PASYN_ADAPTER_EXT			pExt 
)
/*++

Description:

Parameters:

Return Value:

--*/
{
	NTSTATUS Status = STATUS_UNSUCCESSFUL;
	ULONG nLines = 0;
	ULONG iLine = 0;   
	ULONG iSdxbPort;

	// Read Number of Lines from registry
	{
		UNICODE_STRING ParamsSubkey = { 0 };
		HANDLE			hKeyParams = NULL;

		if ( NT_ERROR( DigiStrAllocUnicodeString( &ParamsSubkey, pName->Buffer, L"\\", DXB_REGKEY_PARAMETERS )))
			goto Exit;
		   
			if ( NT_SUCCESS( DigiRegOpenSubkey( &hKeyParams, hKeyServices, ParamsSubkey.Buffer )))
		{      	
			Status = DigiRegQueryDword( hKeyParams, DXB_REGVAL_NUMBEROFLINES, &nLines, DigiRegFlagRequired, 0 );
			DigiRegCloseKey( hKeyParams );
		}	   

		DigiStrFreeUnicodeString( &ParamsSubkey );         

		if ( NT_ERROR( Status ))
			goto Exit;
		   
		DigiDump( DIGIINIT, ( "FindPortsOnAdapter: number of lines = 0x%x\n", nLines ) );
	}   
   
	for ( iSdxbPort = 0, iLine = 0; iLine < nLines; iLine++)
	{
		UNICODE_STRING LinePath = { 0 };
		{   
			UNICODE_STRING LineNumber;
			WCHAR buf[32];
			LineNumber.Buffer = buf;
			LineNumber.Length = 0;
			LineNumber.MaximumLength = sizeof buf;
			Status = RtlIntegerToUnicodeString( iLine, 10, &LineNumber );

			// Create Parameters subkey name, e.g. ASYN\0.  We'll open this subkey.
			if ( NT_SUCCESS(Status) )
				Status = DigiStrAllocUnicodeString( &LinePath, pRegPath->Buffer, L"\\", LineNumber.Buffer );
		}

		if ( NT_SUCCESS(Status) )
		{
			HANDLE hkLine = NULL;

			if (NT_SUCCESS(DigiRegOpenSubkey( &hkLine, hKeyServices, LinePath.Buffer )))
			{
				ULONG iPort = 0;
				UNICODE_STRING PortNumber = {0};
				UNICODE_STRING PortPath = { 0 };
            
            while ( NT_SUCCESS( Status ))
				{   
					HANDLE			hkPort;    
					WCHAR buf[32];
               
               // Build registry path for the next port
					PortNumber.Buffer = buf;
					PortNumber.Length = 0;
					PortNumber.MaximumLength = sizeof buf;
					Status = RtlIntegerToUnicodeString( iPort, 10, &PortNumber );

					// Build parameters subkey name, e.g. ASYN\0\0.  We'll open this subkey.
					if ( NT_SUCCESS(Status) )
						Status = DigiStrAllocUnicodeString( &PortPath, LinePath.Buffer, L"\\", PortNumber.Buffer );
        
        			if ( NT_SUCCESS(Status) )          
						Status = DigiRegOpenSubkey( &hkPort, hkLine, PortNumber.Buffer );
                  
               if ( NT_SUCCESS(Status) )
               {
               	// Port exists - create the device and initialize it.
                  
						UNICODE_STRING	Name = {0};
						UNICODE_STRING SdxbNumber = {0};
						WCHAR buf[32];
						SdxbNumber.Buffer = buf;
						SdxbNumber.Length = 0;
						SdxbNumber.MaximumLength = sizeof buf;
						RtlIntegerToUnicodeString( iSdxbPort, 10, &SdxbNumber );

						if (NT_SUCCESS( DigiStrAllocUnicodeString( &Name, pName->Buffer, DXB_HDWID_ASYN, SdxbNumber.Buffer )))
						{
							PASYN_PORT_EXT	pPortExt;
							UNICODE_STRING DeviceName = {0};
							PDEVICE_OBJECT	pDeviceObject;
						              	
                  
							Status = DigiStrDeviceName( &DeviceName, Name.Buffer );
							DigiDump( DIGIINIT, ("ASYNConstructAllPorts:Found Port %wZ\n",&DeviceName));

							if ( NT_SUCCESS( Status ) )
							{
								Status = IoCreateDevice( gpDriverObject, sizeof *pPortExt, &DeviceName, FILE_DEVICE_SERIAL_PORT, 0, TRUE, &pDeviceObject);
							}

							if ( NT_ERROR( Status ) )
							{
								// IoCreateDevice was unsuccessful: log an error message
								AsynLogImproperDeviceName( Status, &DeviceName );
							}				   
							else
							{
								pPortExt = pDeviceObject->DeviceExtension;
								pPortExt->pDevice = pDeviceObject;
								pPortExt->pParentExt = pExt;
								pPortExt->iSdxbPort = iSdxbPort;
								pPortExt->DsHandle = pExt->DsHandle;
								pPortExt->Type = ASYN_TYPE_PORT_EXT;

								(void)DigiStrAllocUnicodeString( &pPortExt->DeviceName, DeviceName.Buffer, NULL, NULL );

								Status = AsynInitPort( pPortExt, hkPort );

								if (NT_SUCCESS(Status))
								{
									// Link Port Device Object into the adapter device extension
									InsertTailList( &pExt->PortList, &pPortExt->ListEntry );
								}
								else
								{
									// Otherwise free any allocated resources and delete the object
									AsynFreePortResources( pPortExt );
									IoDeleteDevice( pPortExt->pDevice );
								}                        
	                     
							}                     
                     
							DigiRegCloseKey( hkPort );

							if ( Name.Buffer )
								DigiStrFreeUnicodeString( &Name );
							if ( DeviceName.Buffer )
								DigiStrFreeUnicodeString( &DeviceName );                        

							iPort++;
							iSdxbPort++;
						} 
                  
						if ( PortPath.Buffer )
							DigiStrFreeUnicodeString( &PortPath );
                                      
					}                  
				}
				
				DigiRegCloseKey( hkLine );
			}
			// Goofy firmware exposes 24 serial devices for each T1 line, even when 
			// configured for PRI.  Since we don't know how the line has been configured,
			// (we don't even know what kind of card it is), infer it from the number of 
			// ports configured.  If we've setup only 23 serial ports on this line, assume
			// it's a T1 and skip the 24th port before moving on to the next T1 line.  
			// Again, this only applies to Houston T1.												 
			if ( iSdxbPort == 23 /*MAX_PORT_PER_PRI_LINE*/ )
				iSdxbPort++;
		}         
		if ( LinePath.Buffer )
			DigiStrFreeUnicodeString( &LinePath );
	}	         
Exit:   
	// Return succesful if at least one port was found.
	if ( IsListEmpty( &pExt->PortList ))
	{
		AsynLogNoPortsFound( pName );
		Status = STATUS_UNSUCCESSFUL;
	}
	else
	{
		Status = STATUS_SUCCESS;
	}

	DigiDump( DIGIINIT,  ( "FindPortsOnAdapter: Leave, Status 0x%x\n", Status ) );
	return Status;
}   


STATIC NTSTATUS
ConstructAdapter(
	IN HANDLE						hKeyServices,
	IN PUNICODE_STRING			pName,
	IN PUNICODE_STRING			pRegPath,				
	OUT PASYN_ADAPTER_EXT		*ppExt )
/*++

Description:

	Create device object for the adapter, initialize the device extension.  
   
Parameters:

Return Value:


--*/
{
	NTSTATUS Status;
	PDEVICE_OBJECT pDevice = NULL;
	PASYN_ADAPTER_EXT pExt = NULL;
	UNICODE_STRING DeviceName = {0};

	DigiDump( DIGIINIT, ( "ConstructAdapter, adapter %S\n", pName->Buffer ) );

	{
		UNICODE_STRING Buf = {0};

		if ( NT_SUCCESS( DigiStrAllocUnicodeString( &Buf, DXB_HDWID_ASYN, pName->Buffer, NULL ) ) )
		{
			// Create a device object for the adapter
			RtlInitUnicodeString( &DeviceName, NULL );
			Status = DigiStrDeviceName( &DeviceName, Buf.Buffer );
			if ( NT_SUCCESS( Status ) )
			{
				Status = IoCreateDevice( gpDriverObject, sizeof *pExt, &DeviceName, FILE_DEVICE_CONTROLLER, 0, FALSE, &pDevice );
			}

			// Expose this adapter/device to usermode apps via symbolic link
			if ( NT_SUCCESS( Status ) )
			{
				UNICODE_STRING SymbolicName = {0};
				Status = DigiStrSymbolicName( &SymbolicName, Buf.Buffer );

				if ( NT_SUCCESS( Status ) )
				{
					(void)IoCreateSymbolicLink( &SymbolicName, &DeviceName );
					DigiStrFreeUnicodeString( &SymbolicName );
				}
				else
				{
					AsynLogNoSymLink( Status, pName );
				}
			}
			DigiStrFreeUnicodeString( &Buf );
		}

		if ( NT_ERROR( Status ) )
		{
			// IoCreateDevice was unsuccessful: log an error message
			AsynLogImproperDeviceName( Status, &DeviceName );
			goto Exit;
		}	
	}
   
	// Initialize device extension
	pExt = pDevice->DeviceExtension;
	RtlZeroMemory( pExt, sizeof *pExt );
	pExt->Type = ASYN_TYPE_ADAPTER_EXT;
	Status = DigiStrAllocUnicodeString( &pExt->InstanceName, pName->Buffer, NULL, NULL );
	if ( NT_ERROR( Status ) )
		goto Exit;

	DigiInitializeSpinLock( &pExt->Lock );
	InitializeListHead( &pExt->PortList );

	pExt->pDevice = pDevice;

	// Open the streams library
	Status = DsOpenDevice( pName, &pExt->DsHandle);

	if ( NT_ERROR( Status ) )
	{
		AsynLogDsOpenDeviceError( Status, pName );
		goto Exit;
   }

	if ( NT_SUCCESS( Status ) )
	{
// TODO:		IoRegisterShutdownNotification( pExt->pDevice );
	}
   
	Status = FindPortsOnAdapter( hKeyServices, pName, pRegPath, pExt );
   
Exit:

	if ( NT_ERROR( Status ) )
   {
		if ( pDevice != NULL ) 
		{
			UNICODE_STRING Buf = {0};

			if ( pExt->DsHandle != NULL )
			{
				DsCloseDevice( pExt->DsHandle );         
				DigiDump( DIGIINIT,  ( "ConstructAdapter: DsCloseDevice\n" ) );
			}         

			// Delete symbolic link
			if ( NT_SUCCESS( DigiStrAllocUnicodeString( &Buf, DXB_HDWID_ASYN, pName->Buffer, NULL ) ) )
			{
				DigiDump( DIGIINIT,  ( "ConstructAdapter: Delete symbolic link\n", Status ) );
				(void) IoDeleteSymbolicLink( &Buf );
				DigiStrFreeUnicodeString( &Buf );
			}            

			if ( pExt->InstanceName.Buffer )
				DigiStrFreeUnicodeString( &pExt->InstanceName );

			IoDeleteDevice( pDevice );
		}         
	}         

	if ( DeviceName.Buffer )
		DigiStrFreeUnicodeString( &DeviceName );

	if ( NT_SUCCESS( Status ) )
		*ppExt = pExt;

	DigiDump( DIGIINIT,  ( "ConstructAdapter: Leave, Status 0x%x pExt 0x%p\n", Status, pExt ) );
	return Status;
}


NTSTATUS
AsynConstructAllPorts(
	IN HANDLE	hKeyDriver
)
{
	HANDLE	hKeyParams = NULL;
	HANDLE	hKeyServices = NULL;
	NTSTATUS	Status;

	DigiDump( DIGIINIT, ( "ASYN!AsynConstructAllPorts\n") );

	// Build a path to the Services registry key and open it
	{
		UNICODE_STRING Services;
		NTSTATUS Status = DigiStrAllocUnicodeString( &Services, gDriverRegPath.Buffer, NULL, NULL );
		if ( NT_SUCCESS( Status ) )
		{
			while ( (WCHAR)Services.Buffer[Services.Length/sizeof(WCHAR)] != (WCHAR)'\\' )
				Services.Length -= sizeof(WCHAR);
			Services.Buffer[Services.Length/sizeof(WCHAR)] = '\0';

			Status = DigiRegOpenKey( &hKeyServices, Services.Buffer );
			DigiStrFreeUnicodeString( &Services );
		}
	}

	if ( NT_ERROR(DigiRegOpenSubkey( &hKeyParams, hKeyDriver, DXB_REGKEY_PARAMETERS )))
		goto Exit;
   
   {
   	ULONG iSubkey = 0;
      UNICODE_STRING AdapterName = { 0 };
      
      while ( NT_SUCCESS( DigiRegEnumSubkey( hKeyParams, iSubkey, &AdapterName )))
      {
			HANDLE	hKeyInstance = NULL;
			UNICODE_STRING ServiceName = { 0 };
			UNICODE_STRING RegPath = { 0 };
			PASYN_ADAPTER_EXT pAdapterExt = NULL;

			DigiDump( DIGIINIT, ("ASYNConstructAllPorts:Found Adapter %wZ\n",&AdapterName));

			if ( NT_ERROR(DigiRegOpenSubkey( &hKeyInstance, hKeyParams, AdapterName.Buffer )))
				goto Exit;

			Status = DigiRegQuerySz( hKeyInstance, DXB_REGVAL_DEVICE, &ServiceName, DigiRegFlagRequired, NULL );

			if ( NT_SUCCESS( Status ))
				Status = DigiRegQuerySz( hKeyInstance, DXB_REGVAL_REGPATH, &RegPath, DigiRegFlagRequired, NULL);
			   
			if ( NT_SUCCESS( Status ))
			{
				DigiDump( DIGIINIT, ("ASYNConstructAllPorts: ServiceName %wZ RegPath %wZ\n",&ServiceName,&RegPath));

				// Create adapter device object and search for ports configured on adapter.
				Status = ConstructAdapter( hKeyServices, &ServiceName, &RegPath, &pAdapterExt );

				// Add to global device extension list
				if ( NT_SUCCESS( Status ) )
					InsertTailList( &gDeviceExtList, &pAdapterExt->ListEntry );
			}

			iSubkey++;
			DigiRegFreeString( &AdapterName );
			DigiRegCloseKey( &hKeyInstance );
			if ( ServiceName.Buffer )
				DigiRegFreeString( &ServiceName );
			if ( RegPath.Buffer )
				DigiRegFreeString( &RegPath );            
		}
	}
Exit:   
	if ( hKeyParams != NULL )
		DigiRegCloseKey( hKeyParams );
	if ( hKeyServices != NULL )
		DigiRegCloseKey( hKeyServices );      
   
	Status = IsListEmpty( &gDeviceExtList ) ? STATUS_UNSUCCESSFUL : STATUS_SUCCESS;

	if ( Status == STATUS_UNSUCCESSFUL )
		AsynDestructAllPorts( );
	   
	DigiDump( DIGIINIT, ( "ASYN!AsynConstructAllPorts: Leave, Status 0x%x\n", Status) );
	return Status;   
}