/*
 ****************************************************************************
 * Copyright (c) 1990-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    : AsynPnp.c
 *
 * Purpose        : PNP Irp Handler routines
 *
 *----------------------------------------------------------------------
 */
#include "digiasyn.h"
#if NT5_BUILD

#include <initguid.h>
#include <ntddser.h>

struct PNP_IRP_NAME
{
	ULONG Minor;
	PCSTR Name;
};

const struct PNP_IRP_NAME PnpIrpTable[] = 
{
	IRP_MN_START_DEVICE,						"IRP_MN_START_DEVICE",
	IRP_MN_QUERY_STOP_DEVICE, 				"IRP_MN_QUERY_STOP_DEVICE",
	IRP_MN_STOP_DEVICE, 						"IRP_MN_STOP_DEVICE",
	IRP_MN_CANCEL_STOP_DEVICE, 			"IRP_MN_CANCEL_STOP_DEVICE",
	IRP_MN_QUERY_REMOVE_DEVICE, 			"IRP_MN_QUERY_REMOVE_DEVICE",
	IRP_MN_REMOVE_DEVICE, 					"IRP_MN_REMOVE_DEVICE",
	IRP_MN_CANCEL_REMOVE_DEVICE, 			"IRP_MN_CANCEL_REMOVE_DEVICE",
	IRP_MN_SURPRISE_REMOVAL, 				"IRP_MN_SURPRISE_REMOVAL",
	IRP_MN_QUERY_CAPABILITIES, 			"IRP_MN_QUERY_CAPABILITIES",
	IRP_MN_QUERY_PNP_DEVICE_STATE, 		"IRP_MN_QUERY_PNP_DEVICE_STATE",
	IRP_MN_FILTER_RESOURCE_REQUIREMENTS,"IRP_MN_FILTER_RESOURCE_REQUIREMENTS",
	IRP_MN_DEVICE_USAGE_NOTIFICATION, 	"IRP_MN_DEVICE_USAGE_NOTIFICATION",
	IRP_MN_QUERY_DEVICE_RELATIONS, 		"IRP_MN_QUERY_DEVICE_RELATIONS",
	IRP_MN_QUERY_RESOURCES, 				"IRP_MN_QUERY_RESOURCES",
	IRP_MN_QUERY_RESOURCE_REQUIREMENTS, "IRP_MN_QUERY_RESOURCE_REQUIREMENTS",
	IRP_MN_QUERY_ID, 							"IRP_MN_QUERY_ID",
	IRP_MN_QUERY_DEVICE_TEXT, 				"IRP_MN_QUERY_DEVICE_TEXT",
	IRP_MN_QUERY_BUS_INFORMATION, 		"IRP_MN_QUERY_BUS_INFORMATION",
	IRP_MN_QUERY_INTERFACE, 				"IRP_MN_QUERY_INTERFACE",
	IRP_MN_READ_CONFIG, 						"IRP_MN_READ_CONFIG",
	IRP_MN_WRITE_CONFIG, 					"IRP_MN_WRITE_CONFIG",
	IRP_MN_EJECT, 								"IRP_MN_EJECT",
	IRP_MN_SET_LOCK, 							"IRP_MN_SET_LOCK",
	IRP_MN_QUERY_LEGACY_BUS_INFORMATION,"IRP_MN_QUERY_LEGACY_BUS_INFORMATION"
};


STATIC PCSTR GetPnpIrpName( ULONG Minor )
{
	static const CHAR Unknown[] = "Unknown";
	PCSTR Name = Unknown;
	int i;
	for ( i = 0; i < sizeof PnpIrpTable / sizeof PnpIrpTable[0]; i++ )
	{
		if ( Minor == PnpIrpTable[i].Minor )
		{
			Name = PnpIrpTable[i].Name;
			break;
		}
	}
	return Name;
}

VOID INLINE CompletePNPIrp( IN PIRP Irp, NTSTATUS Status )
{
   Irp->IoStatus.Status = Status;
   IoCompleteRequest( Irp, IO_NO_INCREMENT );
}

STATIC NTSTATUS
AsynPnpIrpComplete( 
	IN PDEVICE_OBJECT pDevice,
	IN PIRP           pIrp,
	IN PKEVENT        pEvent )
{
	KeSetEvent( pEvent, IO_NO_INCREMENT, FALSE );
	return STATUS_MORE_PROCESSING_REQUIRED;
}


STATIC NTSTATUS AsynPnpForwardIrp( IN PDEVICE_OBJECT pNextLowerDriver, IN PIRP pIrp )
{
	ASSERT( pNextLowerDriver );
	ASSERT( pIrp );
	IoSkipCurrentIrpStackLocation( pIrp );
	return IoCallDriver( pNextLowerDriver, pIrp );
}


STATIC NTSTATUS AsynPnpForwardIrpWait( IN PDEVICE_OBJECT pNextLowerDriver, IN PIRP pIrp )
{
	KEVENT Event;
	NTSTATUS Status;

	ASSERT( pNextLowerDriver );
	ASSERT( pIrp );
	
	KeInitializeEvent( &Event, SynchronizationEvent, FALSE );

	IoCopyCurrentIrpStackLocationToNext( pIrp );
	IoSetCompletionRoutine( pIrp, AsynPnpIrpComplete, &Event, TRUE, TRUE, TRUE );

	Status = IoCallDriver( pNextLowerDriver, pIrp );
	if ( Status == STATUS_PENDING )
	{
		DigiDump( DIGIPNP, ( "AsynPnpForwardIrp: waiting for Irp 0x%p completion...", pIrp ) );
		KeWaitForSingleObject( &Event, Executive, KernelMode, FALSE, NULL );
		Status = pIrp->IoStatus.Status;
		DigiDump( DIGIPNP, ( "AsynPnpForwardIrp: Irp completed   0x%p, status 0x%x", pIrp, Status ) );
	}
	return Status;
}

STATIC PWCHAR
GetInstanceString( 
	PWSTR pString,
	ULONG Length )
/*++

Description: 

	Parse a WCHAR string, returning a pointer to the device instance.  Expected format
	is "<SomeGuid>\<InstanceNumber>".

Parameters:

	pString
		String to parse

	Length
		Length in WCHARs of the string.

Return Value:

	NULL if not found, otherwise pointer to InstanceNumber string.

--*/
{
	ULONG i = Length;
	if ( pString == NULL || i == 0 )
		return NULL;

	while ( i > 0 && pString[--i] != '\\' )
	{
		;
	}

	if ( i != 0 && i < Length-1 )
		return &pString[i+1];
	else
		return NULL;
}


STATIC NTSTATUS
GetDeviceInstance( 
	IN PDEVICE_OBJECT pPdo,
	IN OUT PWCHAR     buf,
	IN ULONG          cb )
{
	NTSTATUS Status = STATUS_UNSUCCESSFUL;
	WCHAR MyBuf[64] = {0};
	ULONG MyCb;
	Status = IoGetDeviceProperty( pPdo, DevicePropertyDriverKeyName, sizeof MyBuf, 
   										MyBuf, &MyCb );
	if ( NT_SUCCESS( Status ) )
	{
		PWSTR p = GetInstanceString( MyBuf, MyCb/sizeof(WCHAR) );
		if ( p && wcslen(p)*sizeof(WCHAR) < cb )
		{
			wcscpy( buf, p );
			Status = STATUS_SUCCESS;
		}
	}
	return Status;
}

STATIC NTSTATUS 
AsynPnpStartDevice( IN PASYN_PORT_EXT pPortExt, PIRP pIrp )
{
	NTSTATUS Status;
	HANDLE hkDriver = NULL;
	UNICODE_STRING RegPath = {0};
	UNICODE_STRING FullRedirectPath = {0};

	// Forward the IRP to the bus driver and wait for it to come back
	// up before continuing.
	//
	Status = AsynPnpForwardIrpWait( pPortExt->pNextDeviceObject, pIrp );
	DigiDump( DIGIPNP, ( "AsynPnpStartDevice: AsynPnpForwardIrpWait returned status 0x%x\n", Status ) );
	if ( NT_ERROR( Status ) )
		goto Exit;
      
	Status = IoOpenDeviceRegistryKey( pPortExt->pPdo, PLUGPLAY_REGKEY_DRIVER, MAXIMUM_ALLOWED, &hkDriver );
	if ( NT_ERROR( Status ) )
		goto Exit;

	{
		HANDLE hkParameters;
		Status = DigiRegOpenSubkey( &hkParameters, hkDriver, DXB_REGKEY_PARAMETERS );
		if ( NT_SUCCESS( Status ) )
      {
			// The DLL will write this based on adapter type, it will be located at the redirect path
			Status = DigiRegQuerySz( hkParameters, DXB_REGVAL_REGPATH, &RegPath, DigiRegFlagRequired, NULL );
			(void)DigiRegCloseKey( hkParameters );
		}            
		ZwClose( hkDriver );
	}
   
	if ( NT_ERROR( Status ) )
		goto ErrorExit;
   
#define REDIRECTPATH_ROOT L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\Class\\"
	Status = DigiStrAllocUnicodeString( &FullRedirectPath, REDIRECTPATH_ROOT, RegPath.Buffer, NULL );
	if ( NT_ERROR( Status ) )
		goto ErrorExit;
	   
   {
		HANDLE hKey;
		Status = DigiRegOpenSubkey( &hKey, NULL, FullRedirectPath.Buffer );
      if ( NT_SUCCESS( Status ))
		{   
			Status = AsynInitPort( pPortExt, hKey );      
			DigiDump( DIGIPNP, ( "AsynPnpStartDevice: AsynInitPort returned status 0x%x\n", Status ) );
		   DigiRegCloseKey( hKey );
      }   
	}            
   
   if (NT_ERROR(Status))
		goto ErrorExit;
      
   //
   // Try to open the DStreams Library
   //
	Status = DsOpenDevice( &pPortExt->DxbDevice, &pPortExt->DsHandle);
	DigiDump( DIGIPNP, ( "AsynPnpStartDevice: DsOpenDevice returned status 0x%x\n", Status ) );
	if ( NT_ERROR( Status ) )
	{
		AsynLogDsOpenDeviceError( Status, &pPortExt->DxbDevice );
		goto ErrorExit;
   }
   
   // Make the device visible via a device association.
   Status = IoRegisterDeviceInterface(pPortExt->pPdo, (LPGUID)&GUID_CLASS_COMPORT, NULL, 
														   &pPortExt->DeviceInterfaceSymbolicName);
	if ( NT_ERROR( Status ) ) 
	{
		DigiDump( DIGIPNP|DIGIERROR, ("AsynPnpStartDevice: Couldn't register class association ------- for port %wZ\n",
													&pPortExt->PortName));
		pPortExt->DeviceInterfaceSymbolicName.Buffer = NULL;
		goto ErrorExit;
	}
   
   // Now set the symbolic link for the association
	Status = IoSetDeviceInterfaceState( &pPortExt->DeviceInterfaceSymbolicName, TRUE);

	if ( NT_ERROR( Status ) )
	{
		DigiDump( DIGIPNP|DIGIERROR, ("AsynPnpStartDevice: Couldn't enable class association ------- for port %wZ\n",
													&pPortExt->PortName));
		goto ErrorExit;                                       
	}
   
  	pPortExt->PnpState = PNPSTATE_OK;
	pPortExt->DeviceStateFlags = 0;
     
	DigiDump( DIGIPNP, ("ASYN!AsynPnpStartDevice successful: PortName = %S PortNum =%d \n", pPortExt->PortName.Buffer,pPortExt->iSdxbPort));
   goto Exit;

ErrorExit:   
	if ( pPortExt->DeviceInterfaceSymbolicName.Buffer )
	{
		ExFreePool( pPortExt->DeviceInterfaceSymbolicName.Buffer );   
		pPortExt->DeviceInterfaceSymbolicName.Buffer = NULL;
	}   
            
	if ( pPortExt->DsHandle != NULL )
	{
		( void )DsCloseDevice( pPortExt->DsHandle );  
		pPortExt->DsHandle = NULL;
	}      

	AsynFreePortResources( pPortExt );      
   
Exit:
	CompletePNPIrp( pIrp, Status );
	if ( FullRedirectPath.Buffer )  
		DigiStrFreeUnicodeString( &FullRedirectPath );
	if ( RegPath.Buffer )
   	DigiStrFreeUnicodeString( &RegPath );      
	return Status;
}

STATIC NTSTATUS AsynPnpQueryStopDevice( IN PASYN_PORT_EXT pPortExt, PIRP pIrp )
{
   NTSTATUS Status = STATUS_SUCCESS;
   
   // IRP_MN_QUERY_STOP_DEVICE has been received - determine whether the port can 
   // be stopped. If the port is open, we reject the request.

	DigiAcquireSpinLock( &pPortExt->Lock );
   if ( pPortExt->State == IdleState )
   {
      // Yes, we can accept the stop query - update the state so an open request will be rejected
      // and forward irp to the lower bus driver
      pPortExt->PnpState |= PNPSTATE_STOPPING;
		DigiReleaseSpinLock( &pPortExt->Lock );
		Status = AsynPnpForwardIrp( pPortExt->pNextDeviceObject, pIrp );
   }
   else
   {
      // Port is busy - reject the stop query
		DigiReleaseSpinLock( &pPortExt->Lock );
      DigiDump(DIGIPNP, ("AsynQueryStopDevice: IRP_MN_QUERY_STOP_DEVICE: Rejected query for port = %d state = %x\n",
                         pPortExt->iSdxbPort,pPortExt->State));
      Status = STATUS_UNSUCCESSFUL;
      CompletePNPIrp( pIrp, Status );
   }
   return Status;   
}

NTSTATUS AsynPnpStopDevice( IN PASYN_PORT_EXT pPortExt, PIRP pIrp, BOOLEAN fPowerStateChange )
{
   NTSTATUS Status = STATUS_SUCCESS;
   BOOLEAN fCalledInternally = pIrp == NULL ? TRUE : FALSE;
   
	DigiDump( DIGIPNP, ( "DigiAsyn!AsynPnpStopDevice, pPortExt 0x%p, pIrp 0x%p fPowerStateChange %x CurrentIrql %x\n", pPortExt, pIrp, fPowerStateChange, KeGetCurrentIrql() ) );

	DigiAcquireSpinLock( &pPortExt->Lock );
   
   // Update the PNP State so no more requests will be accepted   
   pPortExt->PnpState |= PNPSTATE_STOPPED;
   
   if ( pPortExt->State != IdleState )
   {
		DigiDump( DIGIPNP, ( "\tpPortExt->State %x\n", pPortExt->State ) );
   	// Port is open - complete the IRPs in progress and close the device
	   pPortExt->State = CloseInProgressState;
		DigiReleaseSpinLock( &pPortExt->Lock );
   	(void)AsynDeviceCleanup( pPortExt->pDevice, NULL );
		(void)AsynCloseSdxbPort( pPortExt );      
      // Wait for port to be idle before continuing
	   {
			LARGE_INTEGER Timeout;
			ULONG i;

			Timeout.QuadPart = 50 * ONE_MILLISECOND;
      
			for ( i = 0; i < 20; i++ )
			{
				(void)KeDelayExecutionThread( KernelMode, FALSE, &Timeout );
      	   if ( pPortExt->State == IdleState )	// State is updated in AsynCloseComplete processing
            	break;
			}               
		}
      DigiAcquireSpinLock( &pPortExt->Lock );
   }
   
	{
		HANDLE DsHandle = pPortExt->DsHandle;
      pPortExt->DsHandle = NULL;
		DigiReleaseSpinLock( &pPortExt->Lock );
      if ( DsHandle != NULL )
			(void)DsCloseDevice( DsHandle );
	}	   

	if ( !fPowerStateChange )   
	{
		AsynFreePortResources( pPortExt );
      
		DigiDump( DIGIPNP , ("Free DeviceInterfaceSymbolicName buffer\n"));
      
		if ( pPortExt->DeviceInterfaceSymbolicName.Buffer )
		{      
			Status = IoSetDeviceInterfaceState( &pPortExt->DeviceInterfaceSymbolicName, FALSE);
			if ( pPortExt->DeviceInterfaceSymbolicName.Buffer )
			{
				ExFreePool( pPortExt->DeviceInterfaceSymbolicName.Buffer );   
				pPortExt->DeviceInterfaceSymbolicName.Buffer = NULL;
			}
		}                  
      
	}   
   
	if ( !fCalledInternally )
   {
	   // Forward the IRP to the bus driver.
		Status =  AsynPnpForwardIrp( pPortExt->pNextDeviceObject, pIrp );
	}         
	DigiDump( DIGIPNP , ("Leaving AsynPnpStopDevice: CurrentIrql = %x\n",KeGetCurrentIrql()));
      
   return Status;   
}

STATIC NTSTATUS AsynPnpCancelStopDevice( IN PASYN_PORT_EXT pPortExt, PIRP pIrp )
{
	NTSTATUS Status;
   Status =  AsynPnpForwardIrpWait( pPortExt->pNextDeviceObject, pIrp );
	if ( NT_SUCCESS( Status ) )
   {
   	pPortExt->PnpState = PNPSTATE_OK;
   }
	CompletePNPIrp( pIrp, Status );
   return Status;
}


STATIC NTSTATUS AsynPnpQueryRemoveDevice( IN PASYN_PORT_EXT pPortExt, PIRP pIrp )
{
   NTSTATUS Status = STATUS_SUCCESS;

   // IRP_MN_QUERY_REMOVE_DEVICE has been received, determine whether the port can be removed from the system. 
   // If the port is open, we reject the request.
   if ( pPortExt->State == IdleState )
   {
      // Yes, we can accept the remove query - update the state so an open request will be rejected
		pPortExt->PnpState |= PNPSTATE_REMOVING;
      Status = AsynPnpForwardIrp( pPortExt->pNextDeviceObject, pIrp );
   }
   else
   {
      // Port is busy - reject the remove query
		DigiDump( DIGIPNP, ( "AsynQueryRemoveDevice: Rejected remove query for port = %d state = %x\n",pPortExt->iSdxbPort, pPortExt->State ) );
      Status = STATUS_UNSUCCESSFUL;
      CompletePNPIrp ( pIrp, Status );
   }      
   return Status;   
}

STATIC NTSTATUS AsynPnpRemoveDevice( IN PASYN_PORT_EXT pPortExt, PIRP pIrp )
{
	NTSTATUS Status = STATUS_SUCCESS;

	DigiDump( DIGIPNP, ( "DigiAsyn!AsynPnpRemoveDevice, pPortExt 0xp, pIrp 0x%p\n", pPortExt, pIrp ) );
	// Make sure port has been stopped...
   if (( pPortExt->PnpState & PNPSTATE_STOPPED) != PNPSTATE_STOPPED )
   {
		( void )AsynPnpStopDevice( pPortExt, NULL, FALSE );
   }
   
   if ( pPortExt->DeviceName.Buffer )
		DigiStrFreeUnicodeString( &pPortExt->DeviceName );

	// Forward the IRP to the bus driver
	Status = AsynPnpForwardIrp( pPortExt->pNextDeviceObject, pIrp );
    
   // Remove the device object from the device stack
   IoDetachDevice( pPortExt->pNextDeviceObject );
   
	// Remove from global list of ports
	RemoveEntryList( &pPortExt->ListEntry );

   // Free the device object
   IoDeleteDevice( pPortExt->pDevice );

	// If the last port was removed, clean-up the global device object used for dgamode tracing
	if (IsListEmpty( &gDeviceExtList ))
		AsynDeleteGlobalCardExt();
   
	return Status;
}

STATIC NTSTATUS AsynPnpCancelRemoveDevice( IN PASYN_PORT_EXT pPortExt, PIRP pIrp )
{
	NTSTATUS Status;
	Status =  AsynPnpForwardIrpWait( pPortExt->pNextDeviceObject, pIrp );
	if ( NT_SUCCESS( Status ) )
	{
		pPortExt->PnpState &= ~PNPSTATE_REMOVING;
	}
	CompletePNPIrp( pIrp, Status );
	return Status;
}


STATIC NTSTATUS AsynPnpQueryDeviceState( IN PASYN_PORT_EXT pPortExt, PIRP pIrp )
{
	NTSTATUS Status;
   if ( pPortExt->DeviceStateFlags )
   {
		pIrp->IoStatus.Information |= pPortExt->DeviceStateFlags;
	   DigiDump( DIGIPNP, ("AsynPnpQueryDeviceState: DeviceStateFlags = %x\n",pPortExt->DeviceStateFlags));
	}      
	Status =  AsynPnpForwardIrp( pPortExt->pNextDeviceObject, pIrp );
	return Status;
}


NTSTATUS AsynAddDevice( IN PDRIVER_OBJECT pDriverObject, IN PDEVICE_OBJECT pPhysicalDeviceObject )
{
   NTSTATUS Status = STATUS_SUCCESS;
	UNICODE_STRING DeviceName = {0};
	UNICODE_STRING Name = {0};
   PASYN_PORT_EXT pPortExt;     
	PDEVICE_OBJECT pFunctionalDeviceObject = NULL;
   
   DigiDump( DIGIINIT, ("ASYN!AddDevice:\n"));
   
   // Create a device object for the FDO
	{
		WCHAR buf[16] = {0};
		Status = GetDeviceInstance( pPhysicalDeviceObject, buf, sizeof buf );
		if ( NT_SUCCESS( Status ) )
			Status = DigiStrAllocUnicodeString( &Name, DXB_HDWID_ASYN, buf, NULL );
	}
   
	if ( NT_ERROR( Status ) )
		goto Exit;
   
	Status = DigiStrDeviceName( &DeviceName, Name.Buffer );
   
	if ( NT_SUCCESS( Status ) )
	{
      Status = IoCreateDevice( gpDriverObject, sizeof *pPortExt, &DeviceName, FILE_DEVICE_SERIAL_PORT, 0, TRUE, &pFunctionalDeviceObject);
	}
	if ( NT_ERROR( Status ) )
		goto Exit;
   
	pPortExt = pFunctionalDeviceObject->DeviceExtension;
   
   DigiStrAllocUnicodeString( &pPortExt->DeviceName, DeviceName.Buffer, NULL, NULL );
	pPortExt->Type = ASYN_TYPE_PORT_EXT;
   pPortExt->PnpState = PNPSTATE_STOPPED;
	pPortExt->pPdo = pPhysicalDeviceObject;
	
	pFunctionalDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;

	// Set both PDO and FDO PAGABLE flags.  This forces SET_POWER apis to be called
	// at PASSIVE_LEVEL rather than DISPATCH_LEVEL.
	pFunctionalDeviceObject->Flags |= DO_POWER_PAGABLE|DO_BUFFERED_IO;
	pPhysicalDeviceObject->Flags   |= DO_POWER_PAGABLE;

   // Attach the device object to the device stack 
	pPortExt->pNextDeviceObject = IoAttachDeviceToDeviceStack( pFunctionalDeviceObject, pPhysicalDeviceObject );
   
   pPortExt->pDevice = pFunctionalDeviceObject;

    {
        POWER_STATE State = {0};

        pPortExt->PowerState   = PWRSTATE_ON;  // power assumed to be on
        pPortExt->SysPower     = PowerSystemWorking;
        pPortExt->DevPower     = PowerDeviceD0;
        pPortExt->WakeSysPower = PowerSystemWorking;

        State.DeviceState = PowerDeviceD0;
	    (void)PoSetPowerState( pPortExt->pDevice, DevicePowerState, State );
    }
   
	// Add to global list of ports
	InsertTailList( &gDeviceExtList, &pPortExt->ListEntry );
    
	DigiDump( DIGIINIT|DIGIPNP, ( "AsynAddDevice: DriverObject 0x%p, FDO  0x%p\n"
	                                "               NextLowerDevice 0x%p\n"
	                                "               Name %S\n"
	                                "               Status 0x%x\n",
	                                gpDriverObject,  
	                                pFunctionalDeviceObject,  
	                                pPortExt->pNextDeviceObject, 
	                                Name.Buffer, 
	                                Status ) );
                                   
	
   
Exit:   	 
	if ( DeviceName.Buffer )
   	DigiStrFreeUnicodeString( &DeviceName );
      
	if ( Name.Buffer )  
		DigiStrFreeUnicodeString( &Name );
      
	return Status;
}

NTSTATUS AsynDevicePnp( IN PDEVICE_OBJECT pDevice, PIRP pIrp )
{
	PIO_STACK_LOCATION pIos = IoGetCurrentIrpStackLocation( pIrp );
   PASYN_PORT_EXT pPortExt = pDevice->DeviceExtension;       
	ULONG const Minor = pIos->MinorFunction;
	NTSTATUS Status;
	
	if ( pDevice->DeviceType == FILE_DEVICE_UNKNOWN )
	{
		Status = STATUS_UNSUCCESSFUL;
		pIrp->IoStatus.Status = Status;
		IoCompleteRequest( pIrp, IO_NO_INCREMENT );
		return Status;
	}
	DigiDump( DIGIPNP, ( "DigiAsyn!PnpDispatch: %s, pPortExt 0x%p, pIrp 0x%p\n", GetPnpIrpName(Minor), pPortExt, pIrp ) );

	switch ( Minor )
	{
		case IRP_MN_START_DEVICE:					
			Status = AsynPnpStartDevice( pPortExt, pIrp );	  		
         break;
		case IRP_MN_QUERY_STOP_DEVICE:
      	Status = AsynPnpQueryStopDevice( pPortExt, pIrp );
         break;
		case IRP_MN_STOP_DEVICE:
      	Status = AsynPnpStopDevice( pPortExt, pIrp, FALSE );	  
         break;
		case IRP_MN_CANCEL_STOP_DEVICE:
      	Status = AsynPnpCancelStopDevice( pPortExt, pIrp );
			break;
		case IRP_MN_QUERY_REMOVE_DEVICE:			
      	Status = AsynPnpQueryRemoveDevice( pPortExt, pIrp ); 
         break;
		case IRP_MN_REMOVE_DEVICE:					
      	Status = AsynPnpRemoveDevice( pPortExt, pIrp );
         break;
		case IRP_MN_CANCEL_REMOVE_DEVICE:
      	Status = AsynPnpCancelRemoveDevice( pPortExt, pIrp );
         break;
      /*         
		case IRP_MN_SURPRISE_REMOVAL:	
      	Status = AsynPnpSurpriseRemoval( pPortExt, pIrp );
         break;

		case IRP_MN_QUERY_LEGACY_BUS_INFORMATION:	
			break;
		*/

		case IRP_MN_QUERY_PNP_DEVICE_STATE:			
      	Status = AsynPnpQueryDeviceState( pPortExt, pIrp );
         break;
		default:
			DigiDump( DIGIPNP, ( "DigiAsyn!PnpDispatch: Passing unhandled PNP Irp %s on to PDO!\n", GetPnpIrpName(Minor ) ) );
			Status = AsynPnpForwardIrp( pPortExt->pNextDeviceObject, pIrp );
			break;
	}

	if ( NT_ERROR( Status ) )
	{
		DigiDump( DIGIPNP, ( "AsynPnpDispatch: %s, returning failure status 0x%x\n", GetPnpIrpName(Minor), Status ) );
	}
	
	return Status;
}


NTSTATUS AsynSystemControl( IN PDEVICE_OBJECT pDevice, IN PIRP Irp )
/*++

Description:

	The primary dispatch routine for IRP_MJ_SYSTEM_CONTROL requests.

Parameters:

	Standard NT Dispatch routine parameters.

Return Value:

	STATUS_SUCCESS if request completed successfully.

--*/
{
    PASYN_PORT_EXT pPortExt = pDevice->DeviceExtension;
    NTSTATUS Status;
   
	DigiDump( DIGIPNP, ( "DigiAsyn!AsynSystemControl: Enter, pExt 0x%08x, pIrp 0x%08x\n", 
	          pDevice->DeviceExtension, Irp ) );

    IoSkipCurrentIrpStackLocation(Irp);
    Status = IoCallDriver(pPortExt->pNextDeviceObject, Irp);

	DigiDump( DIGIPNP, ( "DigiAsyn!AsynSystemControl: Leave, pExt 0x%08x, pIrp 0x%08x, Status 0x%x\n", pDevice->DeviceExtension, Irp, Status ) );
    return Status;   
}


#endif  // NT5_BUILD



