/*
 ****************************************************************************
 * 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         
            {
#if TEST
// TODO
                (void) RtlDeleteRegistryValue( RTL_REGISTRY_DEVICEMAP, L"SERIALCOMM", DeviceMapName.Buffer );
#else
                (void) RtlDeleteRegistryValue( RTL_REGISTRY_DEVICEMAP, L"SERIALCOMM", pPortExt->DeviceName.Buffer );
#endif

                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 
)
{   
        int i;

    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      
    // Halt the AsynWriteDispatch worker thread and wait until it's terminated.
    if ( pPortExt->Write.pWorkerThread != NULL )
    {
        ASSERT( KeGetCurrentIrql() == PASSIVE_LEVEL );
        (void)KeSetEvent( &pPortExt->Write.HaltEvent, EVENT_INCREMENT, FALSE );
        (void)KeWaitForSingleObject( pPortExt->Write.pWorkerThread, Executive, KernelMode, FALSE, NULL );
        ObDereferenceObject( pPortExt->Write.pWorkerThread );
        pPortExt->Write.pWorkerThread = NULL;
    }

    // Check to ensure all timers in the DevObjExt are cancelled here 
    // before we kill the Devobj. 

    if ( KeCancelTimer( &pPortExt->Read.Timeout.Timer ) == TRUE )
    {
        SRAPRINT( DBGLVL_ALWAYS, ("DigiAsyn!AsynFreePortResources Read Timeout Timer not cancelled!!!!"));
        ASSERT(0);
    }

    if ( KeCancelTimer( &pPortExt->Read.Timeout.IntervalTimer ) == TRUE )
    {
        SRAPRINT( DBGLVL_ALWAYS, ("DigiAsyn!AsynFreePortResources Read Timeout Interval Timer not cancelled!!!!"));
        ASSERT(0);
    }
    if ( KeCancelTimer( &pPortExt->Close.Timer ) == TRUE )
    {
        SRAPRINT( DBGLVL_ALWAYS, ("DigiAsyn!AsynFreePortResources Close Timer not cancelled!!!!"));
        ASSERT(0);
    }
    if ( KeCancelTimer( &pPortExt->Trace.TraceTimer ) == TRUE )
    {
        SRAPRINT( DBGLVL_ALWAYS, ("DigiAsyn!AsynFreePortResources Trace Timer not cancelled!!!!"));
        ASSERT(0);
    }
    if ( KeCancelTimer( &pPortExt->Write.WriteTimer.Timer ) == TRUE )
    {
        SRAPRINT( DBGLVL_ALWAYS, ("DigiAsyn!AsynFreePortResources WriteTimer Timer not cancelled!!!!"));
        ASSERT(0);
    }

    for( i = 0; i < DS_PRIORITY_COUNT; i++)
    {
       if ( KeCancelTimer( &pPortExt->RxEngine[i].Timer ) == TRUE )
       {
           SRAPRINT( DBGLVL_ALWAYS, ("DigiAsyn!AsynFreePortResources RxEngine Timer not cancelled!!!!"));
           ASSERT(0);
       }
    }
}

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 );
        pExt->DsHandle = NULL;
    }
         
    {
        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 );         
                pExt->DsHandle = NULL;
                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;   
}
