/*
 ****************************************************************************
 * 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    : AsynPnp.c
 *
 * Purpose        : PNP Irp Handler routines
 *
 *----------------------------------------------------------------------
 */
 
#include "header.h"

//
// Expose a global Driver Object to reference later for things like debug reporting
//
DRIVER_OBJECT 	*globalDriverObject;
PCARD_EXT 		pGlobalCardExt;
DEVICE_OBJECT 	*globalCard;

#if NT5_BUILD

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",
};


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%x completion...", pIrp ) );
		KeWaitForSingleObject( &Event, Executive, KernelMode, FALSE, NULL );
		Status = pIrp->IoStatus.Status;
		DigiDump( DIGIPNP, ( "AsynPnpForwardIrp: Irp completed   0x%x, 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 PPORT_EXT pExt, PIRP pIrp )
{
	NTSTATUS Status;
	HANDLE hkDriver = NULL;
	UNICODE_STRING RegPath = {0};
	UNICODE_STRING FullRedirectPath = {0};
   UNICODE_STRING DxbDevice = {0};
   REGISTRY_PORT_VALUES portKeyValues = {0};
   ULONG				SDxbPort = 0;
   

	// Forward the IRP to the bus driver and wait for it to come back
	// up before continuing.
	//
	Status = AsynPnpForwardIrpWait( pExt->pNextDeviceObject, pIrp );
	if ( NT_ERROR( Status ) )
		goto Exit;

	//
   // Read in the DigiAsyn Registry Key values.
   //
	Status = IoOpenDeviceRegistryKey( pExt->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
			//(void)DigiRegQueryDword( hkParameters, DXB_REGVAL_ASYN_MODEMDEVICE, &portKeyValues.IsModem, DigiRegFlagOptional, DXB_REGVAL_ASYN_MODEMDEVICE_DEFAULT );
			Status = DigiRegQuerySz( hkParameters, DXB_REGVAL_REGPATH, &RegPath, DigiRegFlagRequired, NULL );
			(void)DigiRegCloseKey( hkParameters );
		}            
		ZwClose( hkDriver );
	}
   
	if ( NT_ERROR( Status ) )
		goto Exit;
   
#define REDIRECTPATH_ROOT L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\Class\\"
	Status = DigiStrAllocUnicodeString( &FullRedirectPath, REDIRECTPATH_ROOT, 
      													RegPath.Buffer, NULL );

	if ( NT_ERROR( Status ) )
		goto Exit;
   
  	Status = AsynGetPortKeyValues( &FullRedirectPath, NULL, &portKeyValues);

	if ( NT_ERROR( Status ) )
		goto Exit;

   {
		HANDLE hKey;
		Status = DigiRegOpenSubkey( &hKey, NULL, FullRedirectPath.Buffer );
      if ( NT_SUCCESS( Status ))
		{   
		  // The coinstaller (digiasyn.dll) writes the internal modem flag based on adapter type
		  (void)DigiRegQueryDword( hKey, DXB_REGVAL_ASYN_MODEMDEVICE, &portKeyValues.IsModem, DigiRegFlagOptional, DXB_REGVAL_ASYN_MODEMDEVICE_DEFAULT );

		  Status = DigiRegQuerySz( hKey, DXB_REGVAL_DEVICE, &DxbDevice, DigiRegFlagRequired, NULL );
	      if (NT_SUCCESS( Status ))
				Status = DigiRegQueryDword( hKey, DXB_REGVAL_ASYN_SDXB_PORT, &SDxbPort, DigiRegFlagRequired, 0 );

			if (NT_SUCCESS(Status))
         {         
				DigiDump( DIGIPNP, ("AsynPnpStartDevice: DxbDevice = %S SDXB DevNum=%d\n", DxbDevice.Buffer, SDxbPort ) );
			}               
			else
	      {
	      	DigiDump( DIGIPNP, ("AsynPnpStartDevice: Error reading from registry. Status = %x\n", Status));
			}               
		   DigiRegCloseKey( hKey );
      }   
	}            
   {
   	HANDLE hKeyDriver;
		(void)IoOpenDeviceRegistryKey( pExt->pPdo, PLUGPLAY_REGKEY_DEVICE, MAXIMUM_ALLOWED, &hKeyDriver );
		if (NT_ERROR(Status))
	   	goto Exit;
		Status = DigiRegQuerySz( hKeyDriver, DXB_REGVAL_ASYN_PORTNAME, &portKeyValues.SymbolicName, DigiRegFlagRequired, NULL );
		ZwClose( hKeyDriver );
   }

	if ( NT_ERROR( Status ) )
		goto Exit;
   
	pExt->Port = SDxbPort; // adapter-based SDXB minor device number (zero-based)
	pExt->CardExt = pGlobalCardExt;
   /* TODO: Do I need this???
	pExt->Line = lineIndex;
	*/            
   
   //
   // Try to open the DStreams Library
   //
	Status = DsOpenDevice( &DxbDevice, &pExt->DsHandle);
   if (NT_ERROR(Status))
   {
   	AsynLogError(gpDriverObject, ASYN_DSTREAMS_OPEN_ERROR, DxbDevice.Buffer, Status);
      goto Exit;
	}
   
   //
   // Initialize port and build the symbolic name for the port
   //
	Status = AsynInitPort( pExt, &DxbDevice, &portKeyValues );

	if ( NT_ERROR( Status ) )
		goto Exit;
   
  	pExt->PnpState = PNPSTATE_OK;
   DigiStrAllocUnicodeString( &pExt->DxbDevice, DxbDevice.Buffer, NULL, NULL );
	DigiDump( DIGIPNP, ("ASYN!AsynPnpStartDevice: pExt->PnpState = %d\n", &pExt->PnpState));
   
Exit:
	CompletePNPIrp( pIrp, Status );
	if ( FullRedirectPath.Buffer )  
		DigiStrFreeUnicodeString( &FullRedirectPath );
	if ( RegPath.Buffer )
   	DigiRegFreeString( &RegPath );
	if ( DxbDevice.Buffer )
   	DigiRegFreeString( &DxbDevice );  
	if ( portKeyValues.SymbolicName.Buffer )
		DigiRegFreeString( &portKeyValues.SymbolicName );
      
	// If successful, execute scheduler so any queued irps can be dispatched     
	KeAcquireSpinLock( &pExt->PortLock, &pExt->PortIrql);
	if ( pExt->PnpState == PNPSTATE_OK )
   {
   	AsynSchedulerLoop( pExt );
	}                
   KeReleaseSpinLock( &pExt->PortLock, pExt->PortIrql);
 
	return Status;
}

static NTSTATUS AsynPnpQueryStopDevice( IN PPORT_EXT pExt, 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.

	KeAcquireSpinLock( &pExt->PortLock, &pExt->PortIrql);
   if ( pExt->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
      pExt->PnpState |= PNPSTATE_STOPPING;
		KeReleaseSpinLock( &pExt->PortLock, pExt->PortIrql);
		Status = AsynPnpForwardIrp( pExt->pNextDeviceObject, pIrp );
   }
   else
   {
      // Port is busy - reject the stop query
		KeReleaseSpinLock( &pExt->PortLock, pExt->PortIrql);
      DigiDump(DIGIPNP, ("AsynQueryStopDevice: IRP_MN_QUERY_STOP_DEVICE: Rejected query for port = %d state = %x\n",
                         pExt->Port,pExt->State));
      Status = STATUS_UNSUCCESSFUL;
      CompletePNPIrp( pIrp, Status );
   }
   return Status;   
}

NTSTATUS AsynPnpStopDevice( IN PPORT_EXT pExt, PIRP pIrp, BOOLEAN fPowerStateChange )
{
   NTSTATUS Status = STATUS_SUCCESS;
   BOOLEAN fCalledInternally = pIrp == NULL ? TRUE : FALSE;

	KeAcquireSpinLock( &pExt->PortLock, &pExt->PortIrql);
   
   // Update the PNP State so no more requests will be accepted   
   pExt->PnpState |= PNPSTATE_STOPPED;
   
   if ( pExt->State != IdleState )
   {
   	// Port is open - complete the IRPs in progress and close the device
	   pExt->State = ClosingState;
   	(void)AsynCleanup( pExt, NULL );
		AsynCloseRsp( pExt );      
       
      // Wait for port to be idle before continuing
	   {
			LARGE_INTEGER Timeout;
			ULONG i;

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

	if ( !fPowerStateChange )   
		AsynFreePortResources( pExt );
   
	if ( !fCalledInternally )
   {
	   // Forward the IRP to the bus driver.
		Status =  AsynPnpForwardIrp( pExt->pNextDeviceObject, pIrp );
	}         
      
   return Status;   
}

static NTSTATUS AsynPnpCancelStopDevice( IN PPORT_EXT pExt, PIRP pIrp )
{
	NTSTATUS Status;
   Status =  AsynPnpForwardIrpWait( pExt->pNextDeviceObject, pIrp );
	if ( NT_SUCCESS( Status ) )
   {
		KeAcquireSpinLock( &pExt->PortLock, &pExt->PortIrql);
   	pExt->PnpState = PNPSTATE_OK;
   	AsynSchedulerLoop( pExt );
	   KeReleaseSpinLock( &pExt->PortLock, pExt->PortIrql);
   }
	CompletePNPIrp( pIrp, Status );
   return Status;
}


static NTSTATUS AsynPnpQueryRemoveDevice( IN PPORT_EXT pExt, 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 ( pExt->State == IdleState )
   {
      // Yes, we can accept the remove query - update the state so an open request will be rejected
		pExt->PnpState |= PNPSTATE_REMOVING;
      Status = AsynPnpForwardIrp( pExt->pNextDeviceObject, pIrp );
   }
   else
   {
      // Port is busy - reject the remove query
		DigiDump( DIGIPNP, ( "AsynQueryRemoveDevice: Rejected remove query for port = %d state = %x\n",pExt->Port, pExt->State ) );
      Status = STATUS_UNSUCCESSFUL;
      CompletePNPIrp ( pIrp, Status );
   }      
   return Status;   
}

static NTSTATUS AsynPnpRemoveDevice( IN PPORT_EXT pExt, PIRP pIrp )
{
	NTSTATUS Status = STATUS_SUCCESS;

	// Make sure port has been stopped...
   if (( pExt->PnpState & PNPSTATE_STOPPED) != PNPSTATE_STOPPED )
   {
		( void )AsynPnpStopDevice( pExt, NULL, FALSE );
   }
   
   if ( pExt->DeviceName.Buffer )
		DigiStrFreeUnicodeString( &pExt->DeviceName );

	// Forward the IRP to the bus driver
	Status = AsynPnpForwardIrp( pExt->pNextDeviceObject, pIrp );
    
   // Remove the device object from the device stack
   IoDetachDevice( pExt->pPdo );
   
	// Remove from global list of ports
   {
   	KIRQL Irql;
		KeAcquireSpinLock( &pGlobalCardExt->CardLock, &Irql );
		RemoveEntryList( &pExt->PortEntry);
		KeReleaseSpinLock( &pGlobalCardExt->CardLock, Irql );
   }

   // Free the device object
   IoDeleteDevice( pExt->PortDevice );
   
	return Status;
}

static NTSTATUS AsynPnpCancelRemoveDevice( IN PPORT_EXT pExt, PIRP pIrp )
{
	NTSTATUS Status;
   Status =  AsynPnpForwardIrpWait( pExt->pNextDeviceObject, pIrp );
	if ( NT_SUCCESS( Status ) )
   {
		KeAcquireSpinLock( &pExt->PortLock, &pExt->PortIrql);
		pExt->PnpState &= ~PNPSTATE_REMOVING;
      if ( pExt->PnpState == PNPSTATE_OK)
	   	AsynSchedulerLoop( pExt );
	   KeReleaseSpinLock( &pExt->PortLock, pExt->PortIrql);
   }
	CompletePNPIrp( pIrp, Status );
   return Status;
}

NTSTATUS AsynAddDevice( IN PDRIVER_OBJECT pDriverObject, IN PDEVICE_OBJECT pPhysicalDeviceObject )
{
   NTSTATUS Status = STATUS_SUCCESS;
	UNICODE_STRING DeviceName = {0};
	UNICODE_STRING Name = {0};
   PPORT_EXT pExt;        // Com Port Extension pointer.
	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 *pExt, &DeviceName, FILE_DEVICE_SERIAL_PORT, 0, TRUE, &pFunctionalDeviceObject);
	}
	if ( NT_ERROR( Status ) )
		goto Exit;
   
	pExt = pFunctionalDeviceObject->DeviceExtension;
   
   DigiStrAllocUnicodeString( &pExt->DeviceName, DeviceName.Buffer, NULL, NULL );
   pExt->PnpState = PNPSTATE_STOPPED;
	pExt->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;
	pPhysicalDeviceObject->Flags   |= DO_POWER_PAGABLE;

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

	{
		POWER_STATE State = { 0 };
		State.DeviceState = pExt->PowerState = PowerDeviceD0;
		(void)PoSetPowerState( pExt->pNextDeviceObject, DevicePowerState, State );
	}
   
	// Remove from global list of ports
   {
   	KIRQL Irql;
		KeAcquireSpinLock( &pGlobalCardExt->CardLock, &Irql );
		InsertTailList( &pGlobalCardExt->PortList, &pExt->PortEntry);
		KeReleaseSpinLock( &pGlobalCardExt->CardLock, Irql );
   }

	DigiDump( DIGIINIT|DIGIPNP, ( "AsynAddDevice: DriverObject 0x%x, FDO  0x%x\n"
	                                "               NextLowerDevice 0x%x\n"
	                                "               Name %S\n"
	                                "               Status 0x%x\n",
	                                gpDriverObject,  
	                                pFunctionalDeviceObject,  
	                                pExt->pNextDeviceObject, 
	                                Name.Buffer, 
	                                Status ) );
                                   
	
   
Exit:   	 
	if ( DeviceName.Buffer )
   	DigiStrFreeUnicodeString( &DeviceName );
      
	if ( Name.Buffer )  
		DigiStrFreeUnicodeString( &Name );
      
	return Status;
}

NTSTATUS AsynPnpDispatch( IN PDEVICE_OBJECT pDevice, PIRP pIrp )
{
	PIO_STACK_LOCATION pIos = IoGetCurrentIrpStackLocation( pIrp );
   PPORT_EXT pExt = pDevice->DeviceExtension;       
	ULONG const Minor = pIos->MinorFunction;
	NTSTATUS Status;
	
	DigiDump( DIGIPNP, ( "DigiAsyn!PnpDispatch: %s, pExt 0x%08x, pIrp 0x%08x\n", GetPnpIrpName(Minor), pExt, pIrp ) );

	switch ( Minor )
	{
		case IRP_MN_START_DEVICE:					
			Status = AsynPnpStartDevice( pExt, pIrp );	  		
         break;
		case IRP_MN_QUERY_STOP_DEVICE:
      	Status = AsynPnpQueryStopDevice( pExt, pIrp );
         break;
		case IRP_MN_STOP_DEVICE:
      	Status = AsynPnpStopDevice( pExt, pIrp, FALSE );	  
         break;
		case IRP_MN_CANCEL_STOP_DEVICE:
      	Status = AsynPnpCancelStopDevice( pExt, pIrp );
			break;
		case IRP_MN_QUERY_REMOVE_DEVICE:			
      	Status = AsynPnpQueryRemoveDevice( pExt, pIrp ); 
         break;
		case IRP_MN_REMOVE_DEVICE:					
      	Status = AsynPnpRemoveDevice( pExt, pIrp );
         break;
		case IRP_MN_CANCEL_REMOVE_DEVICE:
      	Status = AsynPnpCancelRemoveDevice( pExt, pIrp );
         break;
      /*         
		case IRP_MN_SURPRISE_REMOVAL:	
      	Status = AsynPnpSurpriseRemoval( pExt, pIrp );
         break;
		*/
      /*         
		case IRP_MN_QUERY_DEVICE_RELATIONS:			
      	Status = AsynPnpQueryDeviceRelations( pExt, pIrp );
         break;
		*/         
		default:
			DigiDump( DIGIPNP, ( "DigiAsyn!PnpDispatch: Passing unhandled PNP Irp %s on to PDO!\n", GetPnpIrpName(Minor ) ) );
			Status = AsynPnpForwardIrp( pExt->pNextDeviceObject, pIrp );
			break;
	}

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


#endif



