/*
 ****************************************************************************
 * Copyright (c) 1990-99 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:  init.c
 *
 * Author      :  P. Donovan
 *
 * Purpose     :
 *
 *    Initialize Asyn driver.
 *
 *----------------------------------------------------------------------
 */

#include "header.h"
//#include "sdxbisdn.h"
/*
 *----------------------------------------------------------------------
 * Define Unloadable driver functions.
 *----------------------------------------------------------------------
 */
#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT, DriverEntry)
#if !NT5_BUILD
#pragma alloc_text(INIT, AsynInitAdapters)
#pragma alloc_text(INIT, AsynGetDeviceKey)
#pragma alloc_text(INIT, AsynGetDeviceKeyValues)
#pragma alloc_text(INIT, AsynFindPortsOnDevice)
#pragma alloc_text(INIT, AsynGetPortKeyValues)
#endif
#endif

// Pointer to DriverObject.  Set in DriverEntry.
PDRIVER_OBJECT gpDriverObject;

/*
 *----------------------------------------------------------------------
 *  Data Structure Definitions LOCAL to Module.
 *----------------------------------------------------------------------
 */
#if NT5_BUILD
extern PCARD_EXT	pGlobalCardExt;
#else
// Linked and initialized DigiAsyn device objects.
PDEVICE_OBJECT    AsynCards = NULL;          
#endif

UCHAR Version[] = { VER_FILEVERSION };

// Default run-time options
ULONG gDefaultRunTimeOptions = RUNTIME_SET_LINE_USE_PENDING_IRP | 
                               RUNTIME_SET_BAUD_USE_PENDING_IRP |
                               RUNTIME_FLUSH_USE_PENDING_IRP;

ULONG    IsdnCC = 0;

VOID AsynFreePortResources( PPORT_EXT pPortExt )
{	
	DigiDump( DIGIUNLOAD | DIGIPNP, ("ASYN!AsynFreePortResources\n"));

	{
		KeAcquireSpinLock(&pPortExt->PortLock, &pPortExt->PortIrql);
		if (pPortExt->State != IdleState)
		{
		   DigiDump(DIGIALL, ("*** ASYN!AsynFreePortResources: Port=%d, state=0x%x is not idle\n", pPortExt->Port, pPortExt->State));
		   PORT_ERROR(pPortExt);
		}
		pPortExt->State = ShutdownState;
	   AsynFreePool( &pPortExt->PutEventPool);
	   AsynFreePool( &pPortExt->GetEventPool);
		KeReleaseSpinLock(&pPortExt->PortLock, pPortExt->PortIrql);
	}
   
   {
   	// Undo the external port naming
      //
		DigiDump( DIGIUNLOAD|DIGIPNP, ("ASYN!AsynFreePortResources: Deleting SERIALCOMM device =%wZ\n", &pPortExt->DeviceMapName));
		(void) RtlDeleteRegistryValue(RTL_REGISTRY_DEVICEMAP, SERIALCOMM_PATH, pPortExt->DeviceMapName.Buffer);

		DigiDump( DIGIUNLOAD|DIGIPNP, ("ASYN!AsynFreePortResources: Deleting DosDevices COM name =%wZ\n", &pPortExt->SymbolicName));
		(void) IoDeleteSymbolicLink(&pPortExt->SymbolicName);
	}      
   
   if ( pPortExt->DeviceName.Buffer )
		DigiStrFreeUnicodeString( &pPortExt->DeviceName );
      
#if NT5_BUILD      
	if ( pPortExt->DxbDevice.Buffer )
   	DigiStrFreeUnicodeString( &pPortExt->DxbDevice );      
#endif      
	      
   
}


#if NT5_BUILD
VOID
AsynDeleteGlobalCardExt()
{
	if (pGlobalCardExt)
	{
		IoDeleteSymbolicLink(&pGlobalCardExt->SymbolicName);
		IoDeleteDevice(pGlobalCardExt->CardDevice);
		pGlobalCardExt = NULL;
	}
}


NTSTATUS
AsynCreateGlobalCardExt()
/*
	Create global DeviceObject and SymbolicLink for dgamode.
*/
{
	UNICODE_STRING Name;
	NTSTATUS       Status;
	PDEVICE_OBJECT pDevice;
	PCARD_EXT      pCardExt = NULL;

	#define ASYNSYMBOLICNAME L"\\DosDevices\\DigiAsyn"
	#define ASYNDEVICENAME   L"\\Device\\DigiAsyn"

	RtlInitUnicodeString(&Name, ASYNDEVICENAME);
	Status = IoCreateDevice(gpDriverObject, sizeof(CARD_EXT), &Name, FILE_DEVICE_CONTROLLER, 0, FALSE, &pDevice);
	if ( NT_SUCCESS(Status) )
	{
		pCardExt = pDevice->DeviceExtension;

		pCardExt->DeviceName.Length = 0;
		pCardExt->DeviceName.MaximumLength = sizeof pCardExt->DeviceNameBuffer;
		pCardExt->DeviceName.Buffer = pCardExt->DeviceNameBuffer;
		RtlCopyUnicodeString(&pCardExt->DeviceName, &Name);

		pCardExt->SymbolicName.Length = 0;
		pCardExt->SymbolicName.MaximumLength = sizeof pCardExt->SymbolicNameBuffer;
		pCardExt->SymbolicName.Buffer = pCardExt->SymbolicNameBuffer;
		RtlAppendUnicodeToString(&pCardExt->SymbolicName, ASYNSYMBOLICNAME);
		Status = IoCreateSymbolicLink( &pCardExt->SymbolicName, &pCardExt->DeviceName );

		InitializeListHead(&pCardExt->PortList);
		KeInitializeSpinLock(&pCardExt->CardLock);

		pCardExt->RunTimeOptions = gDefaultRunTimeOptions;
		pCardExt->CardDevice = pDevice;
		pDevice->Flags &= ~DO_DEVICE_INITIALIZING;
	}

	pGlobalCardExt = pCardExt;
	return Status;
}
#endif




/*
 *----------------------------------------------------------------------
 *  Function:  AsynUnload.
 *
 *  Purpose:
 *
 *       Close all DigiAsyn devices and ports and deallocate kernel 
 *       resources used by the driver.
 *
 *
 *  Method:
 *
 *       o Deallocate all the DigiAsyn ports
 *       o Deallocate all the DigiAsyn devices and kernel resources.
 *
 *
 *  Returns:
 *
 *      o Nothing is returned.
 *
 *
 *----------------------------------------------------------------------
 */
VOID AsynUnload(
   IN DRIVER_OBJECT  *DriverObject
   )
{
   PDEVICE_OBJECT pAsynDevice;         // Decommissioned Asyn device
   PCARD_EXT      pCardExt;            // Asyn Extension device
   PPORT_EXT      pPortExt;            // Port Extension structure.
   KIRQL          AsynIrql;            // Asyn lock prior irq level.

   DigiDump( DIGIUNLOAD, ("ASYN!Unload:\n"));

#if NT5_BUILD
	// Delete the Device object created at DriverEntry for DgaMode.
	AsynDeleteGlobalCardExt();
#else
   while (AsynCards)
   {
      /*
       * Advance to next DigiAsyn Device Object.
       */
      pAsynDevice = AsynCards;
      pCardExt = (PCARD_EXT) pAsynDevice->DeviceExtension;

      pCardExt->State = ShutdownState;

      AsynCards = pCardExt->Next;

      KeAcquireSpinLock(&pCardExt->CardLock, &AsynIrql);
      while (!IsListEmpty( &pCardExt->PortList))
      {
         PLIST_ENTRY    pList;

         //
         // Remove port from list of controlled ports.
         //
         pList = RemoveHeadList( &pCardExt->PortList);
         pPortExt = CONTAINING_RECORD( pList, PORT_EXT, PortEntry);
         KeReleaseSpinLock(&pCardExt->CardLock, AsynIrql);
         
			AsynFreePortResources( pPortExt ); 
                 
         // Delete the port device object
			{
			   PDEVICE_OBJECT pPort=pPortExt->PortDevice;          
		      IoDeleteDevice(pPort);
			}
         KeAcquireSpinLock(&pCardExt->CardLock, &AsynIrql);
      }

      KeReleaseSpinLock(&pCardExt->CardLock, AsynIrql);

      //
      // Remove the DStreams device
      //
      DigiDump( DIGIUNLOAD, ("ASYN!Unload: Closing DsHandle=0x%x\n", 
                              pCardExt->DsHandle));
      DsCloseDevice( pCardExt->DsHandle);

      //
      // Remove the user visible symbol link name for this device.
      //
      (void) IoDeleteSymbolicLink( &pCardExt->SymbolicName);
      DigiDump(DIGIUNLOAD, ("ASYN!Unload: REMOVE AsynDevice=0x%x Ext=0x%X.\n", 
                           pAsynDevice, (ULONG) pCardExt));
      IoDeleteDevice( pAsynDevice);
   }
#endif   
   MemPrintQuit();
	DigiDisplayMemoryLeaks();

}

/*
 *----------------------------------------------------------------------
 * Function:  DriverEntry.
 *
 * Purpose:
 *
 *    DigiAsyn Driver startup code.
 *
 *
 * Method:
 *
 *    o Init the DStreams library.
 *    o Link the D/SXB protocol stack if necessary.
 *    o Allocate all DigiAsyn device data structures.
 *    o Establish DigiAsyn Driver interface functions.
 *
 *
 * Returns:
 *
 *    o Return STATUS_SUCCESS when initialization was successful;
 *      otherwise STATUS_UNSUCCESSFUL.
 *
 *
 *----------------------------------------------------------------------
 */
NTSTATUS
DriverEntry(
    IN DRIVER_OBJECT    *DriverObject,
    IN UNICODE_STRING   *RegistryPath
)

{
   NTSTATUS   Status;                 // Kernel call return status

   //
   // Initialize memory.
   //
   DigiInitMem( (ULONG)'nysA');
   MemPrintInitialize( "\\SystemRoot\\digiasyn.log" );

#if DBG
   DigiDebugInit( RegistryPath, DIGIUNLOAD | DIGIINIT | DIGIINIT1 | DIGIPNP | DIGIPOWER | DIGIOPEN | DIGICLOSE | DIGIERRORS, DEBUGMODE_CONSOLE);
                  
//   DIGIERRORS | DIGISDXB | DIGITHREAD | DIGIOPEN | DIGIFLOW | DIGIIOCTL1 | 
//   DIGIPURGE | DIGIWRITE | DIGIREAD | DIGIDXB | DIGIUNLOAD, DEBUGMODE_CONSOLE);

	(void)DbgPrint( "\n"
	                "DigiAsyn!DriverEntry:\n"
	                "   gDebugMask         = 0x%x\n"
	                "   gDebugMode         = 0x%x\n\n",
	                &gDebugMask, &gDebugMode );

#endif
	gpDriverObject = DriverObject;

#if NT5_BUILD
	// Create deviceobject for dgamode
	Status = AsynCreateGlobalCardExt();
	if ( NT_ERROR(Status)  )
		AsynDeleteGlobalCardExt();
#else

   /*
    * Initialize configured DigiAsyn adapters and their respective ports.
    */
   Status = AsynInitAdapters(RegistryPath);
   if (! NT_SUCCESS(Status))
   {
       if (AsynCards == NULL)
           AsynLogError(DriverObject, ASYN_NO_CONFIGURED_DEVICE, NULL, Status);

       return STATUS_UNSUCCESSFUL;
   }
#endif
   
   DriverObject->MajorFunction[IRP_MJ_FLUSH_BUFFERS]  = AsynDispatchEntry;
   DriverObject->MajorFunction[IRP_MJ_WRITE]          = AsynDispatchEntry;
   DriverObject->MajorFunction[IRP_MJ_READ]           = AsynDispatchEntry;
   DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = AsynDispatchEntry;
   DriverObject->MajorFunction[IRP_MJ_CREATE]         = AsynDispatchEntry;
   DriverObject->MajorFunction[IRP_MJ_CLOSE]          = AsynDispatchEntry;
   DriverObject->MajorFunction[IRP_MJ_CLEANUP]        = AsynDispatchEntry;
#if NT5_BUILD
	DriverObject->MajorFunction[IRP_MJ_PNP]				= AsynPnpDispatch;
   DriverObject->MajorFunction[IRP_MJ_POWER]          = AsynPowerDispatch;
	DriverObject->DriverExtension->AddDevice           = AsynAddDevice;
#endif      
   DriverObject->DriverUnload = AsynUnload;
   DigiDump(DIGIINIT, ("ASYN!DriverEntry: Exiting with STATUS_SUCCESS\n"));
   
   return Status;
}

/*
 *----------------------------------------------------------------------
 *  Function:  AsynInitAdapters
 *
 *  Purpose:
 *
 *      Locate all configured Adapters and their respectively configured
 *      ports.
 *
 *
 *  Method:
 *
 *      o Interrogate the Registry for configured Adapters
 *      o Create driver specific data structures (CARD OBJECT).
 *      o Create individual device objects for each Adapter port
 *        to be referenced (PORT OBJECT).
 *      o Allocate memory the driver will require to support all ports.
 *      o Start the system thread associated with this Adapter.
 *
 *
 *  Returns:
 *
 *      o Return STATUS_SUCCESS when initialization of configured devices
 *        is successful; otherwise STATUS_UNSUCCESSFUL.
 *
 *
 *----------------------------------------------------------------------
 */
#if !NT5_BUILD
static NTSTATUS
AsynInitAdapters( 
    IN UNICODE_STRING   *RegistryPath
)
{
   NTSTATUS Status;           // Kernel API return value.
   NTSTATUS cardStatus;       // Status value is success WHENEVER any device is configured.
   DEVICE_OBJECT *card;       // Adapter Object Pointer.
   PCARD_EXT pCardExt;        // Controller Extension Pointer.
   THREAD_INFO *threadInfo;   // Worker thread data.
   UNICODE_STRING deviceName; // \Device\DigiAsyn##
   UNICODE_STRING path;       // Registry path to start PS search.
   UNICODE_STRING KeyName;    // Registry DigiDxb## Key.
   UNICODE_STRING DxbInstPath;// DigiDxb##\Parameters\DigiAsyn##
   HANDLE	hKeyServices=NULL;
   HANDLE   hKey ;

   DigiDump( DIGIINIT, ("ASYN!InitAdapters:\n"));
   cardStatus = STATUS_UNSUCCESSFUL;

   /*
    * Initialize the Device name once.
    */
   Status = AsynUniStringInit( &deviceName, ASYN_REG_PATH_LENGTH);
   if (! NT_SUCCESS(Status))
   {
      return (Status);
   }

   //
   // Allocate a unicode string buffer to be used to interrogate the
   // registry for configured DigiAsyn devices.  The buffer will
   // initially start at the RegistryPath\Parameters location in the Registry.
   //
   Status = AsynUniStringInit( &path, 
                               (USHORT)(RegistryPath->MaximumLength + 
                                sizeof(PARAMETERS_PATH) +
                                ASYN_REG_PATH_LENGTH));

   if (! NT_SUCCESS(Status))
   {
      DigiFreeMem(deviceName.Buffer);
      return (Status);
   }

	// Build a path to the Services registry key and open it
	{
		UNICODE_STRING Services;
		Status = DigiStrAllocUnicodeString( &Services, RegistryPath->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 );
		   DigiDump( DIGIINIT1, ("ASYN!InitAdapters:DigiRegOpenKey status=%x %x\n",Status,hKeyServices));
			DigiStrFreeUnicodeString( &Services );
		}
	}

   //
   // The registry path is:  DigiAsyn\Parameters.
   //
   (void) RtlAppendUnicodeStringToString(&path, RegistryPath);
   (void) RtlAppendUnicodeToString(&path, PARAMETERS_PATH);

   //
   // Initialize and allocate space for the Dxb Instance Registry Path
   //
   Status = AsynUniStringInit( &DxbInstPath, ASYN_REG_PATH_LENGTH);
   if (! NT_SUCCESS(Status))
   {
      return (Status);
   }

   for (; TRUE; 
       //
       // Setup for the next Instance of DigiDxb"##" Registry Key.  
       // Remove the added path deliminator.
       //
       path.Length = path.Length - (KeyName.Length + sizeof(WCHAR))
       )
   {
      NTSTATUS logMessage;          // Error log message

      //
      // The Path will have the DigiDxb"##" Key name appended
      // onto the path to expedite key lookup in the Registry.
      //
      // KeyName will be a unicode_string pointing within the Path 
      // unicode_string.
      //
      RtlInitUnicodeString(&KeyName, NULL);

      //
      // Get the next instance and update our path from 
      // "...DigiAsyn\Parameters\" to "...DigiAsyn\Parameters\DigiDxb"##"\" 
      //
      Status = AsynGetDeviceKey(&path, &KeyName);

      if ((Status == STATUS_NO_MORE_ENTRIES) ||
          (! NT_SUCCESS(Status)))
      {
         //
         // Either no DigiDxb "##" Keys were found, all done, a
         // major bug in the driver occurred, or a system failure.
         //
         break;
      }

      //
      // Build NULL terminated \Device\DigiAsynDigiDxb## device name.
      //
      deviceName.Length = 0;
      RtlZeroMemory(deviceName.Buffer, deviceName.MaximumLength);

      RtlAppendUnicodeToString(&deviceName, DIGIASYN_DEVICE);
      RtlAppendUnicodeStringToString(&deviceName, &KeyName);

      //
      // Create an object for each DigiAsyn Device.
      //
      Status = IoCreateDevice(gpDriverObject, sizeof(CARD_EXT),
                              &deviceName,
                              FILE_DEVICE_CONTROLLER,
                              0, FALSE, &card);
      if (! NT_SUCCESS(Status))
      {
         AsynLogError(gpDriverObject, ASYN_IMPROPER_DEVICE_NAME, 
                      deviceName.Buffer, Status);
         continue;
      }
      else
      {
         pCardExt = card->DeviceExtension;
         pCardExt->CardDevice = card;

         RtlZeroMemory(pCardExt, sizeof(CARD_EXT));

         //
         // Now copy the device's name into the unicode_string
         // buffer.  
         //
         pCardExt->DeviceName.MaximumLength = sizeof(pCardExt->DeviceNameBuffer);
         pCardExt->DeviceName.Buffer = &pCardExt->DeviceNameBuffer[0];

         //
         // Copy the created device name.
         //
         RtlAppendUnicodeStringToString(&pCardExt->DeviceName, &deviceName);
      }

      if (!NT_SUCCESS(Status))
      {
         AsynLogError(gpDriverObject, ASYN_INVALID_DEVICE_KEY, NULL, Status);

         IoDeleteDevice(card);

         continue;
      }

      //
      // Read in the DigiAsyn Registry Key values.
      //
      Status = AsynGetDeviceKeyValues(&path, pCardExt, &DxbInstPath);
      if (! NT_SUCCESS(Status))
      {
         AsynLogError(gpDriverObject, ASYN_INVALID_DEVICE_KEY, NULL, Status);

         IoDeleteDevice(card);

         continue;
      }

      // we need to check for the ISDN CC key.
      Status = DigiRegOpenSubkey (&hKey, hKeyServices, DxbInstPath.Buffer);
      if ( NT_SUCCESS(Status))
      {
	     DigiRegQueryDword (hKey, DXB_REGVAL_ASYN_ISDN_CC, &pCardExt->IsdnCC, 
		                    DigiRegFlagOptional, DXB_REGVAL_ASYN_ISDN_CC_DEFAULT);
		 DigiRegCloseKey ( hKey );
		 DigiDump (DIGIINIT, ("The ISDN CC Flag is set to %x\n", pCardExt->IsdnCC));
	  }
	  else
	  {
         AsynLogError(gpDriverObject, ASYN_INVALID_DEVICE_KEY, NULL, Status);

         IoDeleteDevice(card);

         continue;
      }

      //
      // Try to open the DStreams Library
      //
      Status = DsOpenDevice( &KeyName, &pCardExt->DsHandle);
      if (!NT_SUCCESS(Status))
      {
         AsynLogError(gpDriverObject, ASYN_DSTREAMS_OPEN_ERROR,
                      deviceName.Buffer, Status);
         IoDeleteDevice(card);
         continue;
      }

      //
      // Build the symbolic name for the Card.
      //
      AsynUniStringInitBuffer( &pCardExt->SymbolicName, 
                               sizeof(pCardExt->SymbolicNameBuffer), 
                               &pCardExt->SymbolicNameBuffer[0]);

      RtlAppendUnicodeToString( &pCardExt->SymbolicName, REG_DELIMITER);
      RtlAppendUnicodeToString( &pCardExt->SymbolicName, 
                                 DXB_REGVAL_ASYN_DOSDEVICES);
      RtlAppendUnicodeToString( &pCardExt->SymbolicName, REG_DELIMITER);
      RtlAppendUnicodeToString( &pCardExt->SymbolicName, DIGIASYN_PREFIX);
      RtlAppendUnicodeStringToString(&pCardExt->SymbolicName, &KeyName);
      DigiDump( DIGIINIT, ("ASYN!InitAdapters: Card Symbolic name =%wZ\n", 
                           &pCardExt->SymbolicName));

      Status = IoCreateSymbolicLink( &pCardExt->SymbolicName,
                                     &deviceName);

      DigiDump( DIGIINIT, ("ASYN!InitAdapters: RunTimeOptions=0x%x\n", 
               pCardExt->RunTimeOptions));

      //
      // Initialize DigiAsyn control structures that are not zero
      // values.
      //
      pCardExt->State = IdleState;

      KeInitializeSpinLock( &pCardExt->CardLock);
   
      DigiDump( DIGIINIT, ("ASYN!InitAdapters: About to init Queues\n"));
#if REMOVED      
		// SAH - 6/29/99
   	//  don't see where pCardExt->activePorts queue is ever used...
      InitializeListHead( &pCardExt->ActivePorts);
#endif
      InitializeListHead( &pCardExt->PortList);
      
      //
      // Link all cards together.
      //
      pCardExt->Next = AsynCards;
      AsynCards = card;

      //
      // Found at least ONE DigiAsyn Registry Key!
      //
      cardStatus = STATUS_SUCCESS;

      //
      // Locate all DigiAsyn Ports which have been configured into
      // the Registry and create device objects, symbolic objects, and
      // Registry entries.
      //
      DigiDump( DIGIINIT, ("ASYN!InitAdapters: Find ports\n"));
      Status = AsynFindPortsOnDevice( card, &KeyName, &DxbInstPath, hKeyServices);
      if (!NT_SUCCESS(Status))
      //
      // No Port Registry Keys were found.
      //
      {
         AsynLogError( gpDriverObject, ASYN_NEED_PORT_CONFIGURATION, 
                      pCardExt->DeviceName.Buffer, STATUS_SUCCESS);
      }
      if (!NT_SUCCESS(Status))
      {
         DigiDump( DIGIALL, 
                    ("*** ASYN!InitAdapters: Adapter ERROR Init FAILED Status=0x%x!\n", Status));
         IoDeleteDevice(card);
      }
   }

   DigiFreeMem(DxbInstPath.Buffer);
   DigiFreeMem(path.Buffer);
   DigiFreeMem(deviceName.Buffer);
	DigiRegCloseKey( hKeyServices );
   	

   if (!NT_SUCCESS(cardStatus))
   {
      MemPrintQuit();
		DigiDisplayMemoryLeaks();
   }

   //
   // Return the previous Status; i.e., a Controller was
   // successfully configured then status would contain
   // STATUS_SUCCESS.
   //
   return (cardStatus);
}
#endif
/*
 *----------------------------------------------------------------------
 *  Function:  AsynGetDeviceKey.
 *
 *  Purpose:
 *
 *      Locates the next DigiAsyn registry key.
 *
 *
 *  Method:
 *
 *      o Get the Driver attributes.
 *      o Open the Driver key in the Registry.
 *      o Acquire the "next" Driver subkey.  (Should be of the form:
 *        "##".)
 *      o Append the subkey name to the Driver's registry path.
 *      o Advance the index value for the next time this function is
 *        called.
 *      o Close the Driver key in the Registry.
 *
 *
 *  NOTE:
 *
 *      Do not keep the KEY open!!  Too many error conditions may
 *      occur in the calling function.
 *
 *
 *  Returns:
 *
 *      o Returns STATUS_SUCCESS when key is found; otherwise, 
 *        STATUS_INVALID_HANDLE, STATUS_ACCESS_DENIED, 
 *        STATUS_INSUFFICIENT_RESOURCES, STATUS_NO_MORE_ENTRIES.
 *
 *
 *----------------------------------------------------------------------
 */
static NTSTATUS
AsynGetDeviceKey(
   IN OUT UNICODE_STRING  *PsPath,     // Path to find DigiAsyn key.
   OUT UNICODE_STRING     *Key         // Points to DigiAsyn Key in PsPath 
   )
{
   // DigiDxb "##" key index.
   static ULONG psKeyIndex = 0;

   // Handle to a DigiDxb "##" key.
   HANDLE psHandle;

   // Kernel status return value.
   NTSTATUS    Status;

   ULONG       psKeyInfoLength;        /* Size of enumerated key. */
   UNICODE_STRING psKeyString;         /* Unicode string for enumerated key. */
   KEY_BASIC_INFORMATION 
               *psKeyInfo;             /* Registry Key information. */
   OBJECT_ATTRIBUTES
               psAttributes;           /* Driver's attribute value. */

   DigiDump( DIGIINIT, ("ASYN!GetDeviceKey:\n"));

   //
   // Get Driver's attributes to prepare to open the registry key.
   //
   InitializeObjectAttributes(&psAttributes, PsPath,
                              OBJ_CASE_INSENSITIVE, NULL, NULL);

   //
   // Open the Driver's key.
   //
   Status = ZwOpenKey(&psHandle, KEY_READ, &psAttributes);
   if (! NT_SUCCESS(Status))
   {
      return (Status);
   }

   //
   // Get memory to store key info.
   //
   psKeyInfo = (PKEY_BASIC_INFORMATION)
               DigiAllocMem(NonPagedPool, ASYN_REG_PATH_LENGTH);
   if (psKeyInfo == NULL)
   {
      ZwClose(psHandle);
      return (STATUS_INSUFFICIENT_RESOURCES);
   }

   //
   // Get the Driver's index'th item.  The key is "discovered" in this
   // manner because the keys could be named non-incrementally.
   //
   Status = ZwEnumerateKey(psHandle, psKeyIndex, KeyBasicInformation, 
                           psKeyInfo, ASYN_REG_PATH_LENGTH, 
                           &psKeyInfoLength);
   if ((! NT_SUCCESS(Status)) ||
       (psKeyInfoLength > ASYN_REG_PATH_LENGTH))
   {
      ZwClose(psHandle);
      DigiFreeMem(psKeyInfo);

      return (Status);
   }

   psKeyIndex++;

   //
   // Use PsKeyInfo buffer.
   //
   psKeyString.Length = (USHORT) psKeyInfo->NameLength;
   psKeyString.MaximumLength = (USHORT) psKeyInfo->NameLength;
   psKeyString.Buffer = (PWSTR) psKeyInfo->Name;

   //
   // Deliminate the Port's Key with the DigiDxb "##" Key.
   //
   (void) RtlAppendUnicodeToString(PsPath, L"\\");

   //
   // Point Key at the Driver's subkey of DigiDxb##.
   //
   Key->Length = psKeyString.Length;
   Key->MaximumLength = psKeyString.MaximumLength;
   Key->Buffer = PsPath->Buffer + (PsPath->Length / sizeof(WCHAR));

   //
   // Append the key onto the Driver's Registry path to be used to
   // traverse the DigiDxb subkey for COM "##".
   //
   (void) RtlAppendUnicodeStringToString(PsPath, &psKeyString);

   //
   // Cleanup before exiting.
   //
   ZwClose(psHandle);
   DigiFreeMem(psKeyInfo);

   return (STATUS_SUCCESS);
}
/*
 *----------------------------------------------------------------------
 *  Function:  AsynGetDeviceKeyValues.
 *
 *  Purpose:
 *
 *      Fill in the registry values associated with a DigiAsyn device.
 *
 *
 *  Method:
 *
 *      o Setup the values to be read from the registry.
 *      o Get the values from the registry.
 *
 *
 *  Returns:
 *
 *      o Returns STATUS_SUCCESS when key is found; otherwise, 
 *        STATUS_INVALID_PARAMETER, or STATUS_OBJECT_NAME_NOT_FOUND.
 *
 *
 *----------------------------------------------------------------------
 */
static NTSTATUS
AsynGetDeviceKeyValues(
   IN PUNICODE_STRING      pDevPath,        // DigiAsyn key path.
   IN PCARD_EXT            pCardExt,        // Device object extension.
   IN OUT PUNICODE_STRING  pDxbInstPath
   )

{
   NTSTATUS    Status;                 // Kernel status return value.
   ULONG       Zero = 0;               // Default data entry.
   ULONG       i;

#define MAX_CARD_REG_ENTRIES           4
   RTL_QUERY_REGISTRY_TABLE
      devInfo[MAX_CARD_REG_ENTRIES];   // DigiDxb "##" key values to read.

   DigiDump( DIGIINIT, ("ASYN!GetDeviceKeyValues:\n"));

   RtlZeroMemory(devInfo, sizeof(devInfo));

   i = 0;
   devInfo[i].Flags = RTL_QUERY_REGISTRY_DIRECT;
   devInfo[i].Name = REG_DXB_INSTANCE_PATH;
   devInfo[i].EntryContext = pDxbInstPath;
   devInfo[i].DefaultType = REG_SZ;
   devInfo[i].DefaultData = NULL;
   devInfo[i].DefaultLength = pDxbInstPath->MaximumLength;
   
   i++;
   devInfo[i].Flags = RTL_QUERY_REGISTRY_DIRECT;
   devInfo[i].Name = L"RunTimeOptions";
   devInfo[i].EntryContext = &pCardExt->RunTimeOptions;
   devInfo[i].DefaultType = REG_DWORD;
   devInfo[i].DefaultData = &gDefaultRunTimeOptions;
   devInfo[i].DefaultLength = sizeof(ULONG);

#if 0
   i++;
   devInfo[i].Flags = RTL_QUERY_REGISTRY_DIRECT;
   devInfo[i].Name = DXB_REGVAL_ASYN_ISDN_CC;
   devInfo[i].EntryContext = &pCardExt->IsdnCC;
   devInfo[i].DefaultType = REG_DWORD;
   devInfo[i].DefaultData = &IsdnCC ;
   devInfo[i].DefaultLength = sizeof(ULONG);
#endif
   //
   // devInfo[MAX_CARD_REG_ENTRIES] is set to a NULL entry to indicate end-of-record.
   //

   ASSERT(++i <= MAX_CARD_REG_ENTRIES);
#undef   MAX_CARD_REG_ENTRIES

   //
   // Registry path must be NULL terminated.
   //
   if (pDevPath->Length >= pDevPath->MaximumLength)
      Status = STATUS_UNSUCCESSFUL;
   else
   {
      pDevPath->Buffer[ pDevPath->Length/sizeof(WCHAR)] = (WCHAR) NULL;
      Status = RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE, pDevPath->Buffer, 
                                      devInfo, NULL, NULL);
   }

   if ( ! NT_SUCCESS(Status))
   {
      Status = STATUS_UNSUCCESSFUL;
   }
   return (Status);
}

static void AsynInitPortTimerDPCs( PPORT_EXT pPortExt )
{
   KeInitializeDpc( &pPortExt->Timer.IntervalReadDpc, AsynReadIntervalTimedOut, pPortExt);
   KeInitializeTimer( &pPortExt->Timer.IntervalRead);
   
   KeInitializeDpc( &pPortExt->Timer.ReadDpc, AsynReadTimedOut, pPortExt);
   KeInitializeTimer( &pPortExt->Timer.Read);

   KeInitializeDpc( &pPortExt->Timer.WriteDpc, AsynWriteTimedOut, pPortExt);
   KeInitializeTimer( &pPortExt->Timer.Write);

   KeInitializeDpc( &pPortExt->Timer.CloseDpc, AsynCloseTimedOut, pPortExt);
   KeInitializeTimer( &pPortExt->Timer.Close);

}

static void AsynInitPortQueues( PPORT_EXT pPortExt )
{
   InitializeListHead( &pPortExt->ReadQueue);
   InitializeListHead( &pPortExt->WriteQueue);
   InitializeListHead( &pPortExt->CompleteIrpQ);
   InitializeListHead( &pPortExt->SdxbRspQ);
   InitializeListHead( &pPortExt->DispatchQ);
   InitializeListHead( &pPortExt->DispatchStatusQ);
   InitializeListHead( &pPortExt->CancelledAndTimedoutIrpQ);
   InitializeListHead( &pPortExt->PutQ);
   InitializeListHead( &pPortExt->RxQ);
   // we need to init some additional things if isdn cc is selected. tds
   InitializeListHead (&pPortExt->XnGetQueue);
   InitializeListHead (&pPortExt->XnGetPktQueue);
   

#if REMOVED   
	// SAH - 6/29/99: Don't see where this is ever used.
   InitializeListHead( &pPortExt->ActivePort);
#endif
   
	(void)AsynInitPool( &pPortExt->PutEventPool, sizeof(PUT_MSG_EV));
   (void)AsynInitPool( &pPortExt->GetEventPool, sizeof(GET_MSG_EV));
   (void)AsynInitPool( &pPortExt->ReadBufferPool, sizeof(RX_BUF));
   (void)AsynInitPool( &pPortExt->VariableReadBufferPool, 0 );   
}
static void AsynInitPortTraceSettings( PPORT_EXT pPortExt )
{
   //
   // Initialize the Trace stuff
   //
   InitializeListHead( &pPortExt->Trace.Queue);
   KeInitializeTimer( &pPortExt->Trace.Timer);
   KeInitializeDpc( &pPortExt->Trace.Dpc, AsynTraceTimeout, pPortExt);
   pPortExt->Trace.Timeout.QuadPart = 500 * -1 * MILLISECOND;
}


static void AsynInitPortSdxbValues( PPORT_EXT pPortExt )
{ 
	//
	// Initialize the default S/DXB port settings
	//
	pPortExt->SdxbValues.Speed = ASYN_DEF_BAUDRATE;
	//
	// Default line control protocol, is 7E1
	//
	pPortExt->SdxbValues.Cflags = SDXB_CREAD | SDXB_CS7 | SDXB_PARENB;
	pPortExt->SdxbValues.Iflags = SDXB_INPCK;
	pPortExt->SdxbValues.Xflags = 0;

	//
	// Default Modem signals are:
	// DTR enabled and RTS/CTS flow control
	pPortExt->SdxbValues.Mout = SDXB_DTR;
	pPortExt->SdxbValues.Mflow = SDXB_RTS;


	pPortExt->SpecialChars.XonChar = ASYN_DEF_XON;
	pPortExt->SpecialChars.XoffChar = ASYN_DEF_XOFF;

	pPortExt->SdxbValues.Eint = SDXB_TXB | SDXB_TXI | SDXB_RXB | 
	                            SDXB_IPS | SDXB_OPU | SDXB_OPS | 
	                            SDXB_OPX | SDXB_OPH | SDXB_IPU | 
	                            SDXB_IPS;
	//
	// All modem signals will be reported to the driver.
	//
	pPortExt->SdxbValues.Mint = SDXB_DTR | SDXB_RTS | SDXB_CTS | 
	                            SDXB_DSR | SDXB_RI | SDXB_DCD;

	//
	// Establish the read side window parameters.
	//
	pPortExt->SdxbValues.Rmax = SDXB_RMAX_DEF;
	pPortExt->SdxbValues.Rtime = SDXB_RTIME_DEF;

	pPortExt->SdxbValues.Tmax = SDXB_TMAX_DEF;
	pPortExt->SdxbValues.Ttime = SDXB_TTIME_DEF;

	pPortExt->SdxbValues.Ltime = SDXB_LTIME_DEF;

	pPortExt->SdxbValues.Maxlen = SDXB_MAXLEN_DEF;
}

NTSTATUS
AsynInitPort( 
	IN PPORT_EXT pPortExt,
   IN PUNICODE_STRING   	pCardName,			// Adapter instance name
   IN REGISTRY_PORT_VALUES	*PortValues    	// Configurable port settings
	)
{
	NTSTATUS 			Status = STATUS_SUCCESS;

   DigiDump( DIGIINIT1, ("ASYN!InitPort\n"));
   
   pPortExt->CloseTimeOut = PortValues->CloseTimeOut;
   pPortExt->CompleteWhenSent = (BOOLEAN)PortValues->CompleteWhenSent;
   pPortExt->QWritesInAdapter = (BOOLEAN)PortValues->QWritesInAdapter;
   pPortExt->IsModem = (BOOLEAN)PortValues->IsModem;
   //
   // Initialize the port's symbolic name UNICODE_STRING.
   //
   AsynUniStringInitBuffer( &pPortExt->SymbolicName, sizeof(pPortExt->SymbolicNameBuffer), &pPortExt->SymbolicNameBuffer[0]);

   pPortExt->PortDevice->Flags |= DO_BUFFERED_IO;

   //
   // Prevent re-entrancy into port.
   //
   KeInitializeSpinLock(&pPortExt->PortLock);

   (void)AsynInitPortQueues( pPortExt );
   
   (void)AsynInitPortTimerDPCs( pPortExt );

	(void)AsynInitPortTraceSettings( pPortExt );   
   
	(void)AsynInitPortSdxbValues( pPortExt );

	pPortExt->State = IdleState;
   
   //
   // Build the symbolic name for the port.
   //
   RtlAppendUnicodeToString( &pPortExt->SymbolicName, REG_DELIMITER);
   RtlAppendUnicodeToString( &pPortExt->SymbolicName, 
                           DXB_REGVAL_ASYN_DOSDEVICES);
   RtlAppendUnicodeToString( &pPortExt->SymbolicName, REG_DELIMITER);
   RtlAppendUnicodeStringToString( &pPortExt->SymbolicName, &PortValues->SymbolicName);
   DigiDump( DIGIINIT, ("ASYN!InitPort: Port COM name = %wZ\n", &pPortExt->SymbolicName));
	DigiDump( DIGIINIT, ("ASYN!InitPort: PortDeviceName = %S\n",pPortExt->DeviceName.Buffer));
   Status = IoCreateSymbolicLink( &pPortExt->SymbolicName, &pPortExt->DeviceName);
   
   if (NT_ERROR(Status))
   {
    	AsynLogError( gpDriverObject, ASYN_IMPROPER_SYMBOLIC_NAME, pPortExt->SymbolicName.Buffer, Status);
      goto AsynInitPortExit;
	}      
   
	{
	   PWCHAR pWs;

		//
		// Record the "##" part in the "COM##" name
		//
		pWs = &pPortExt->SymbolicName.Buffer[0];
		pPortExt->ComNumber = 0;

		// Skip the non numeric part.
		while (*pWs && (*pWs<L'0' || *pWs>L'9'))
		   pWs++;
		// Parse the numeric part
		while (*pWs && *pWs>=L'0' && *pWs<=L'9')
		{
		   pPortExt->ComNumber *= 10;
		   pPortExt->ComNumber += *pWs - L'0';
		   pWs++;
		}

		//
		// Initialize the DEVICEMAP Registry name.
		//
		AsynUniStringInitBuffer( &pPortExt->DeviceMapName, 
		                         sizeof(pPortExt->DeviceMapNameBuffer), 
		                         &pPortExt->DeviceMapNameBuffer[0]);

		//
		// Build the DEVICEMAP name from the parts.
		//
		RtlAppendUnicodeToString( &pPortExt->DeviceMapName, 
		                          DIGIASYN_PREFIX);
		RtlAppendUnicodeStringToString( &pPortExt->DeviceMapName,
		                                pCardName);
		RtlAppendUnicodeStringToString( &pPortExt->DeviceMapName,
		                                &PortValues->SymbolicName);

		//
		// Add appropriate Registry entry under SERIALCOMM Registry key.
		//
		// Use the symbolic name without the path for the value
		// of the DEVICEMAP Registry name.
		//
		// The structure used will reference the value stored
		// in the COM_EXT->SymbolicName.
		//
		(void) RtlWriteRegistryValue(
		                            RTL_REGISTRY_DEVICEMAP,
		                            SERIALCOMM_PATH,
		                            pPortExt->DeviceMapName.Buffer,
		                            REG_SZ,
		                            PortValues->SymbolicName.Buffer,
		                            PortValues->SymbolicName.Length+sizeof(WCHAR));

		DigiDump( DIGIINIT1, ("ASYN!InitPort: Adding SERIALCOMM device = %wZ SymbolicName = %S\n", &pPortExt->DeviceMapName,PortValues->SymbolicName.Buffer));
	}
AsynInitPortExit:
   return Status;                           

}                           
	
/*
 *----------------------------------------------------------------------
 *  Function:  AsynFindPortsOnDevice.
 *
 *  Purpose:
 *
 *      Locate and create port device objects for a DigiAsyn "Controller".
 *
 *
 *  Method:																						   
 *
 *      o Locate a configured port.
 *      o Create a device object for the port.
 *      o Create a symbolic name for the port.
 *      o Create a Registry entry for the port.
 *
 *
 *  Returns:
 *
 *      o Nothing is returned.
 *
 *
 *----------------------------------------------------------------------
 */
static NTSTATUS
AsynFindPortsOnDevice(
   // DigiAsyn Object Pointer.
   IN PDEVICE_OBJECT    Card,         

   // CardName with instance 
   // of the type DigiDxb"X"
   IN PUNICODE_STRING   pCardName,

   // full path for DigiAsyn instance, 
   // of the type "...\DigiAsyn\Parameters\DigiDxb"X
   IN PUNICODE_STRING   RegPath,
   
   IN HANDLE 				hKey
   )
{
   NTSTATUS deviceStatus;     // Kernel API return value.
   NTSTATUS Status;           // Intermediate status value.
   PCARD_EXT pCardExt;        // Controller Extension Pointer.
   PDEVICE_OBJECT pComPort;   // Port Object Pointer.
   PPORT_EXT pPortExt;        // Com Port Extension pointer.
   ULONG portIndex;           // Port Index on a line 
   ULONG lineIndex;  
   ULONG iSdxbPort = 0;       // zero-based S/DXB COM port number
   UNICODE_STRING PortDeviceName;         // \Device\DigiAsynDigiDxb#COM##.          
   REGISTRY_PORT_VALUES portKeyValues;    // Serial DigiAsyn port. 

   DigiDump( DIGIINIT1, ("ASYN!FindPortsOnDevice: RegPath=%wZ\n", RegPath));

   pCardExt = Card->DeviceExtension;
   pCardExt->PhysicalPorts = 0;

   //
   // Setup the Port Device Name UNICODE_STRING.
   //
   deviceStatus = AsynUniStringInit(&PortDeviceName, ASYN_REG_PATH_LENGTH);
   if (! NT_SUCCESS(deviceStatus))
   {
      return (deviceStatus);
   }

   //
   // Initially setup the structure.
   //
   RtlZeroMemory(&portKeyValues, sizeof(portKeyValues));
   portKeyValues.SymbolicName.MaximumLength = 
       sizeof(portKeyValues.SymbolicNameBuffer);
   portKeyValues.SymbolicName.Buffer = &portKeyValues.SymbolicNameBuffer[0];
   for (lineIndex = 0; lineIndex < MAX_LINE_NUMBER; lineIndex++)
   {
      UNICODE_STRING numberUString;
      UNICODE_STRING lineUString;
      WCHAR numberBuf[ASYN_REG_PATH_LENGTH];
      WCHAR lineBuf[ASYN_REG_PATH_LENGTH];

      //
      // Build the lineUString
      //              Adapter Instance + Line Instance
      //                     |                  |
      //                     |                  |
      //                     V                  V
      //  "...DigiAsyn\Parameters\DigiDxb"X" + "Y"
      //
      AsynUniStringInitBuffer(&lineUString, sizeof(lineBuf), lineBuf);
      AsynUniStringInitBuffer(&numberUString, sizeof(numberBuf), numberBuf);
      RtlAppendUnicodeStringToString( &lineUString, RegPath);
      RtlAppendUnicodeToString( &lineUString, LINE_PATH );
      RtlIntegerToUnicodeString( lineIndex, 10, &numberUString );
      RtlAppendUnicodeStringToString( &lineUString, &numberUString );

      DigiDump( DIGIINIT1, ("ASYN!FindPortsOnDevice: Enum lines root=%wZ\n", &lineUString));
      Status = RtlCheckRegistryKey( RTL_REGISTRY_SERVICES, lineUString.Buffer);
      if ( !NT_SUCCESS(Status))
      {
         continue;
      }
      for (portIndex = 0; portIndex < MAX_PORT_NUMBER; portIndex++)
      {
         UNICODE_STRING portUString;
         WCHAR portBuf[ASYN_REG_PATH_LENGTH];
   
         //
         // Build the portUString
         //              Adapter Instance + Line Instance + Port Instance
         //                     |                  |            |
         //                     |                  |            |
         //                     V                  V            V
         //  "...DigiAsyn\Parameters\DigiDxb"X" + "Y"    +     "Z"
         //
         AsynUniStringInitBuffer( &portUString, sizeof(portBuf), portBuf);
         AsynUniStringInitBuffer( &numberUString, sizeof(numberBuf), numberBuf);
         RtlAppendUnicodeStringToString( &portUString, &lineUString);
         RtlAppendUnicodeToString( &portUString, PORT_PATH );
         RtlIntegerToUnicodeString( portIndex, 10, &numberUString );
         RtlAppendUnicodeStringToString( &portUString, &numberUString );
         Status = RtlCheckRegistryKey( RTL_REGISTRY_SERVICES, portUString.Buffer);
         if ( !NT_SUCCESS(Status))
         {
            break;
         }
         Status = AsynGetPortKeyValues( &portUString, hKey, &portKeyValues);
         if ( !NT_SUCCESS(Status))
         {
            break;
         }
         
         //
         // Build NULL terminated \Device\DigiAsynDigiDxb"X"COM## device name.
         //
         PortDeviceName.Length = 0;
         RtlAppendUnicodeToString( &PortDeviceName, DIGIASYN_DEVICE);
         RtlAppendUnicodeStringToString( &PortDeviceName, pCardName);
         RtlAppendUnicodeStringToString( &PortDeviceName, 
                                         &portKeyValues.SymbolicName);
         Status = IoCreateDevice( gpDriverObject, sizeof(PORT_EXT),
                                  &PortDeviceName,
                                  FILE_DEVICE_SERIAL_PORT,
                                  0, TRUE, &pComPort);
	      DigiDump( DIGIINIT1, ("ASYN!FindPortsOnDevice: IoCreateDevice for %wZ status = %x\n", &PortDeviceName,Status));
         if (NT_SUCCESS(Status))
         {
            pPortExt = pComPort->DeviceExtension;
				DigiStrAllocUnicodeString( &pPortExt->DeviceName, PortDeviceName.Buffer, NULL, NULL );

            pPortExt->PortDevice = pComPort;
            pPortExt->Port = iSdxbPort++;	// The adapter-based SDXB minor device number

            //
            // Save the Registry values into the Device Object.
            //
            pPortExt->Card = Card;
            pPortExt->CardExt = pCardExt;
            pPortExt->Line = lineIndex;
            
            Status = AsynInitPort( pPortExt, pCardName, &portKeyValues );
            
            if (NT_SUCCESS(Status))
            {
               //
               // Successfully added at least one port
               //
               deviceStatus = STATUS_SUCCESS;
					//
					// Link COM Device Object into the DigiAsyn
					// Device Object.
					//
					InsertTailList( &pCardExt->PortList, &pPortExt->PortEntry);

               pCardExt->PhysicalPorts++;
            }
            else
            {

               IoDeleteDevice(pComPort);
            }
         }
         else
         {
            //
            // Device object could not be created.  Error Recovery time.
            //
            AsynLogError(gpDriverObject, ASYN_IMPROPER_DEVICE_NAME, 
                         PortDeviceName.Buffer, Status);
         }
      }
#if !NT5_BUILD
      // For NT5, we handle this in the installer

      // 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 == MAX_PORT_PER_PRI_LINE )
         iSdxbPort++;
#endif // #if !NT5_BUILD
   }

   DigiFreeMem( PortDeviceName.Buffer);
   return (deviceStatus);
}
/*
 *----------------------------------------------------------------------
 *  Function:  AsynGetPortKeyValues.
 *
 *  Purpose:
 *
 *      Find a Registry Key for each configured DigiAsyn Port.
 *
 *
 *  Method:
 *
 *      o Get the DigiAsyn Port Key attributes.
 *      o The registry path now has the DigiAsyn Key.
 *      o Acquire the "next" DigiAsyn subkey.  (Should be of the form:
 *        "##".)
 *      o Close the DigiAsyn key in the Registry.
 *      o Append the Port's Key PortName to the DigiAsyn's registry path.
 *      o Setup the values to be read from the registry.
 *      o If any default values are used, ASSUME the key is invalid.
 *      o Get the values from the registry.
 *
 *
 *  NOTE:
 *
 *      Do not keep the KEY open!!  Too many error conditions may
 *      occur in the calling function.
 *
 *
 *  Returns:
 *
 *      o Returns STATUS_SUCCESS when key is found; otherwise, 
 *        STATUS_INVALID_PARAMETER, STATUS_OBJECT_NAME_NOT_FOUND,
 *        STATUS_INVALID_HANDLE, STATUS_ACCESS_DENIED, 
 *
 *
 *----------------------------------------------------------------------
 */
NTSTATUS
AsynGetPortKeyValues(
   IN PUNICODE_STRING	RegistryPath,       
	IN HANDLE				hParentKey,   
   IN OUT REGISTRY_PORT_VALUES
                        *PortValues    // Port Key values.
)

{
   NTSTATUS    Status;                 // Kernel Status return value.
   HANDLE hKey = NULL;
   ULONG       DefCloseTimeout = DXB_REGVAL_ASYN_CLOSETIMEOUT_DEFAULT;
   ULONG       DefCompleteWhenSent = DXB_REGVAL_ASYN_COMPLETEWHENSENT_DEFAULT; 
   ULONG       DefQWritesInAdapter = DEF_Q_WRITES_IN_ADAPTER;

   DigiDump( DIGIINIT1, ("ASYN!GetPortKeyValues:\n"));
	Status = DigiRegOpenSubkey( &hKey, hParentKey, RegistryPath->Buffer );
	if ( NT_ERROR( Status ) )
   {
		goto Exit;
	}      
   //
   // Read the Port's values.
   //
	(void)DigiRegQueryDword( hKey, DXB_REGVAL_ASYN_CLOSETIMEOUT, &PortValues->CloseTimeOut, DigiRegFlagOptional , DXB_REGVAL_ASYN_CLOSETIMEOUT_DEFAULT);      
	(void)DigiRegQueryDword( hKey, DXB_REGVAL_ASYN_COMPLETEWHENSENT, &PortValues->CompleteWhenSent, DigiRegFlagOptional, DXB_REGVAL_ASYN_COMPLETEWHENSENT_DEFAULT );
   (void)DigiRegQueryDword( hKey, QUEUE_WRITES_IN_ADAPTER, &PortValues->QWritesInAdapter,	DigiRegFlagOptional, DEF_Q_WRITES_IN_ADAPTER );
#if !NT5_BUILD
   (void)DigiRegQueryDword( hKey, DXB_REGVAL_ASYN_MODEMDEVICE, &PortValues->IsModem, DigiRegFlagOptional, DXB_REGVAL_ASYN_MODEMDEVICE_DEFAULT );
	Status = DigiRegQuerySz( hKey, DXB_REGVAL_ASYN_DOSDEVICES, &PortValues->SymbolicName, DigiRegFlagRequired, NULL );
#endif   
   DigiRegCloseKey( hKey );
Exit: 
   DigiDump( DIGIINIT1, ("ASYN!GetPortKeyValues Status=%x\n",Status));
	return Status;  
}      
   
