
/*****************************************************************************
* Copyright (c) 1990-98 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
*
*****************************************************************************/

#define UNICODE
#include <ntddk.h>

// Streams Headers
#include "types.h"
#include "wan_proto.h"
#include "wan_control.h"
 
#include "dsapi.h"
#include "dsioctl.h"
#include "dsmp.h"
#include "dgsna.h"
#include "ioctls.h"
#include "pnp.h"

#include "digilib.h"
#include "dxbreg.h"

#if NT5_BUILD



NTSTATUS
DgSnaAddDevice( 
	IN PDRIVER_OBJECT pDriverObject,
	IN PDEVICE_OBJECT pPdo
	)
{
	PPORT_EXT			pFdoExt;
	PDEVICE_OBJECT		pFdo = NULL;
	NTSTATUS			Status;

	DigiDump( DIGIPNP, ( "DIGISNA!DgSnaAddDevice: Enter pPdo 0x%x\n", pPdo ) );

	// Create the SNA Port FDO.  We need to create a separate FDO Device.  The
	// Start Device will find the Link Service associated with this port and
	// Create the Device that the SNA Server Opens.  Two devices must be used,
	// since this line may be configured for SNA, but no link service is 
	// configured for this port.
	//
	// When SNA Server (and IBM CommServer) support plug-and-play the SNA
	// device will need to be a filter driver.
	Status = IoCreateDevice(pDriverObject,
							sizeof (PORT_EXT),
							NULL,
							FILE_DEVICE_DS,
							0,
							FALSE,
							&pFdo);

	if (NT_SUCCESS( Status ))
	{
		ASSERT(pFdo != NULL);

		pFdo->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.
		pFdo->Flags	|= DO_POWER_PAGABLE;
		pPdo->Flags |= DO_POWER_PAGABLE;

		pFdoExt					= pFdo->DeviceExtension;
		pFdoExt->DeviceType		= SNA_FDO_DEVICE_EXT;
		pFdoExt->pPdo			= pPdo;
		pFdoExt->pDeviceObject	= pFdo;

		pFdoExt->pNextDeviceObject = IoAttachDeviceToDeviceStack(	pFdo,pPdo );

		pFdoExt->PnpState = DIGIPNP_ADDED;

		// Initialize the Power State
		{
			POWER_STATE State;
			State.DeviceState = pFdoExt->PowerState = PowerDeviceD0;
			(void)PoSetPowerState( pFdo, DevicePowerState, State );
		}

	}

	DigiDump( DIGIPNP, ( "DIGISNA!DgSnaAddDevice: Leave pPdo 0x%x, Status = %x\n", pPdo, Status ) );

	return Status;
}

NTSTATUS
DgSnaFdoStartDevice(
	PPORT_EXT				pFdoExt, 
	PIRP					pIrp, 
	PIO_STACK_LOCATION		pIos
	)
/*++

Description:

	Handle an IRP_MN_START_DEVICE Irp.  The PnP Manager sends this IRP after it has 
	assigned resources, if any, to the device. The device may have been recently 
	enumerated and is being started for the first time, or the device may be 
	restarting after being stopped for resource reconfiguration. 

	The PnP Manager sends this IRP at IRQL PASSIVE_LEVEL in the context of a system 
	thread.

Parameters:

	pFdoExt
		Device extension for our FDO which describes the adapter.
	
	pIrp
		Request.  May be NULL, if called from within IRP_MN_SET_POWER to come out of
		hibernation.

	pIos
		Parameters passed along with Irp. May be NULL, if called from within 
		IRP_MN_SET_POWER to come out of hibernation.

Return Value:

	STATUS_SUCCESS, or appropriate error status.

--*/
{
	NTSTATUS			Status				= STATUS_SUCCESS;
	HANDLE				hkDriver			= NULL;
	HANDLE				hkParameters		= NULL;
	BOOLEAN				fCalledInternally	= pIrp == NULL ? TRUE : FALSE;
	UNICODE_STRING		RegPath				= {0};
	UNICODE_STRING		FullRedirectPath	= {0};
	UNICODE_STRING		LinkServicePath		= {0};
	UNICODE_STRING		SnaDevice			= {0};
	PDEVICE_OBJECT		pSdo				= NULL;

	DigiDump( DIGIPNP, ( "DIGISNA!DgSnaFdoStartDevice: pFdoExt 0x%x\n", pFdoExt ) );
	
	if ( pFdoExt->PnpState != DIGIPNP_ADDED )
	{
		DigiDump( DIGIPNP, ( "DIGISNA!DgSnaFdoStartDevice: *** Bad PnpState 0x%x! ***\n", pFdoExt->PnpState ) );
		goto Exit;
	}

	// Let the lower driver process the start device first
	if ( !fCalledInternally )
	{
		Status = DigiPnpForwardIrpWait( pFdoExt->pNextDeviceObject, pIrp );

		if ( NT_ERROR( Status ) )
			goto Exit;

	}

	Status = IoOpenDeviceRegistryKey( pFdoExt->pPdo, PLUGPLAY_REGKEY_DRIVER, MAXIMUM_ALLOWED, &hkDriver );

	if ( NT_SUCCESS( Status ) )
	{
		HANDLE hkParameters;
		Status = DigiRegOpenSubkey( &hkParameters, hkDriver, DXB_REGKEY_PARAMETERS );
		if ( NT_SUCCESS( Status ) )
		{
			// Read the REGPATH == registry path) defining this object.  Provides a
			// link back to the subkey under the dxb adapter instance.
			Status = DigiRegQuerySz( hkParameters, DXB_REGVAL_REGPATH, &RegPath, DigiRegFlagRequired, NULL );
			(void)DigiRegCloseKey( hkParameters );

		}
		ZwClose( hkDriver );
	}

	if ( NT_SUCCESS( Status ) )
	{
		DigiDump(DIGIPNP,("DIGISNA!DgSnaFdoStartDevice: RegPath = %S\n",RegPath.Buffer));
		
		Status = DigiStrAllocUnicodeString( &FullRedirectPath, DXB_REDIRECTPATH_ROOT, RegPath.Buffer, NULL );

		if( !NT_SUCCESS( Status ) )
			goto Exit;

		Status = DigiRegOpenKey( &hkDriver, FullRedirectPath.Buffer );

		DigiDump(DIGIPNP,("DIGISNA!DgSnaFdoStartDevice: FullRedirectPath = %S\n",FullRedirectPath.Buffer));

		// Find the Unique Name for the SNA device and Open the Device.
		if ( NT_SUCCESS( Status ) )
		{
			Status = DigiRegQuerySz( hkDriver, DXB_REGVAL_SNA_DEVICE, &SnaDevice, DigiRegFlagRequired, NULL );

			if ( NT_SUCCESS( Status ) )
			{
				Status = DgSnaInitializeDevice(&SnaDevice,gpDriverObject,&pSdo);

				if ( NT_SUCCESS( Status ) )
				{

					// Link the FDO Device to the Sna Device
					pFdoExt->pSdo = pSdo;

					// Signal that this device is done with initialization.
					pSdo->Flags &= ~DO_DEVICE_INITIALIZING;

				}
			}
			ZwClose( hkDriver );

		}

	}

	if ( NT_SUCCESS( Status ) )
	{
		KIRQL Irql;

		pFdoExt->PnpState = DIGIPNP_STARTED;
	}

	if ( !fCalledInternally )
		DgSnaIoCompleteRequest( pIrp, Status, 0 );

Exit:

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

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

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

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

	return Status;
}


NTSTATUS
DgSnaFdoQueryRemoveDevice(
	PPORT_EXT				pFdoExt, 
	PIRP					pIrp, 
	PIO_STACK_LOCATION		pIos
	)
/*++

Description:

	Handle an IRP_MN_QUERY_REMOVE_DEVICE Irp.  The PnP Manager sends this IRP to 
	inform drivers that a device is about to be removed from the system and to query 
	whether the device can be removed without disrupting the system.

	The PnP Manager sends this IRP at IRQL PASSIVE_LEVEL in the context of a system 
	thread.

Parameters:

	pExt
		Device extension for our FDO which describes the adapter.
	
	pIrp
		Request.

	pIos
		Parameters passed along with Irp.

Return Value:

	STATUS_SUCCESS, or appropriate error status.

--*/
{
	NTSTATUS	Status = STATUS_UNSUCCESSFUL;
	KIRQL		Irql;

	DigiDump( DIGIPNP, ( "DIGISNA!DgSnaFdoQueryRemoveDevice: pFdoExt 0x%x , pSdoExt 0x%x\n", pFdoExt, pFdoExt->pSdo ) );

	if (pFdoExt->pSdo)
	{

		PPORT_EXT	pSdoExt = pFdoExt->pSdo->DeviceExtension;

		KeAcquireSpinLock(&pSdoExt->ownership, &Irql);

		if (pSdoExt->state == IdleState )
		{

			// We can Remove it, continue
			pIrp->IoStatus.Status = STATUS_SUCCESS;
			pSdoExt->state = PendingRemove;
			KeReleaseSpinLock(&pSdoExt->ownership,Irql);
			DigiDump( DIGIPNP, ( "DIGISNA!DgSnaFdoQueryRemoveDevice: Can Remove pFdoExt = 0x%x, State = %d\n", pFdoExt, pSdoExt->state) );
			Status = DigiPnpForwardIrp( pFdoExt->pNextDeviceObject, pIrp );
		}
		else
		{

			// We can't Remove it, reject the request now
			KeReleaseSpinLock(&pSdoExt->ownership,Irql);
			DigiDump( DIGIPNP, ( "DIGISNA!DgSnaFdoQueryRemoveDevice: Can't Remove pFdoExt = 0x%x, State = %d\n", pFdoExt, pSdoExt->state) );
			DgSnaIoCompleteRequest( pIrp, Status, 0 );
		}

	}
	else
	{
		// We can Remove it, continue
		DigiDump( DIGIPNP, ( "DIGISNA!DgSnaFdoQueryRemoveDevice: Can Remove 1 pFdoExt = 0x%x\n", pFdoExt) );
		pIrp->IoStatus.Status = Status;
		Status = DigiPnpForwardIrp( pFdoExt->pNextDeviceObject, pIrp );
	}

	return Status;
}



NTSTATUS
DgSnaFdoRemoveDevice(
	PPORT_EXT				pFdoExt, 
	PIRP					pIrp, 
	PIO_STACK_LOCATION		pIos
	)
/*++

Description:

	Handle an IRP_MN_REMOVE_DEVICE Irp.  The PnP Manager uses this IRP to direct 
	drivers to remove a device from their device list. 

	For an orderly device removal, the PnP Manager sends an IRP_MN_QUERY_REMOVE_DEVICE
	prior to the remove IRP. In this case, the device is in the remove-pending state 
	when the remove IRP arrives. For a surprise-style device removal, the physical 
	device has already been removed so the PnP Manager sends the remove IRP without a 
	prior query-remove. A device can be in any state when it receives a remove IRP as 
	a result of a surprise-style removal.

	The PnP Manager sends this IRP at IRQL PASSIVE_LEVEL in the context of a system 
	thread.

Parameters:

	pExt
		Device extension for our FDO which describes the adapter.
	
	pIrp
		Request.

	pIos
		Parameters passed along with Irp.

Return Value:

	This routine must return STATUS_SUCCESS.

--*/
{
	PDEVICE_OBJECT const	pNext	= pFdoExt->pNextDeviceObject;
	PDEVICE_OBJECT			pSdo	= pFdoExt->pSdo;

	DigiDump( DIGIPNP, ( "DIGISNA!DgSnaFdoRemoveDevice: pFdoExt 0x%x , pSdo 0x%x\n", pFdoExt, pSdo ) );

	if (pSdo)
	{
		// Remove the Sna Device
		DgSnaRemoveSnaDevice( pSdo );
	}
	
	// Detach from the Device Stack
	IoDetachDevice(	pNext );

	// Remove the FDO
	IoDeleteDevice( pFdoExt->pDeviceObject );

	return DigiPnpForwardIrpWait( pNext, pIrp );

}


NTSTATUS
DgSnaFdoCancelRemoveDevice(
	PPORT_EXT				pFdoExt, 
	PIRP					pIrp, 
	PIO_STACK_LOCATION		pIos
	)
/*++

Description:

	Handle an IRP_MN_CANCEL_REMOVE_DEVICE Irp.  The PnP Manager uses this IRP to 
	inform the drivers for a device that the device will not be removed.

	The PnP Manager sends this IRP at IRQL PASSIVE_LEVEL in the context of a system 
	thread.

Parameters:

	pExt
		Device extension for our FDO which describes the adapter.
	
	pIrp
		Request.

	pIos
		Parameters passed along with Irp.

Return Value:

	STATUS_SUCCESS, or appropriate error status.

--*/
{
	NTSTATUS	Status = STATUS_SUCCESS;
	KIRQL		Irql;

	DigiDump( DIGIPNP, ( "DIGISNA!DgSnaFdoCancelRemoveDevice: pFdoExt 0x%x , pSdoExt 0x%x\n", pFdoExt, pFdoExt->pSdo ) );

	pIrp->IoStatus.Status = STATUS_SUCCESS;
	Status = DigiPnpForwardIrpWait( pFdoExt->pNextDeviceObject, pIrp );

	return Status;
}


NTSTATUS
DgSnaFdoQueryStopDevice(
	PPORT_EXT				pFdoExt, 
	PIRP					pIrp, 
	PIO_STACK_LOCATION		pIos
	)
/*++

Description:

	Handle an IRP_MN_QUERY_STOP_DEVICE Irp.  The PnP Manager sends this IRP to query 
	whether a device can be stopped for resource reconfiguration.

	The PnP Manager sends this IRP at IRQL PASSIVE_LEVEL in the context of a system 
	thread.

Parameters:

	pExt
		Device extension for our FDO which describes the adapter.
	
	pIrp
		Request.

	pIos
		Parameters passed along with Irp.

Return Value:

	STATUS_SUCCESS, or appropriate error status.

--*/
{
	NTSTATUS	Status = STATUS_UNSUCCESSFUL;
	KIRQL		Irql;

	DigiDump( DIGIPNP, ( "DIGISNA!DgSnaFdoQueryStopDevice: pFdoExt 0x%x , pSdoExt 0x%x\n", pFdoExt, pFdoExt->pSdo ) );

	if (pFdoExt->pSdo)
	{

		PPORT_EXT	pSdoExt = pFdoExt->pSdo->DeviceExtension;

		KeAcquireSpinLock(&pSdoExt->ownership, &Irql);

		if (pSdoExt->state != IdleState )
		{
			// We can Stop it, continue
			pIrp->IoStatus.Status = Status;
			pSdoExt->state = PendingStop;
			KeReleaseSpinLock(&pSdoExt->ownership,Irql);
			Status = DigiPnpForwardIrp( pFdoExt->pNextDeviceObject, pIrp );
		}
		else
		{
			// We can't Stop it, reject the request now
			KeReleaseSpinLock(&pSdoExt->ownership,Irql);
			DgSnaIoCompleteRequest( pIrp, Status, 0 );
		}
	}
	else
	{
		// We can Stop it, continue
		pIrp->IoStatus.Status = Status;
		Status = DigiPnpForwardIrp( pFdoExt->pNextDeviceObject, pIrp );
	}

	return Status;
}



NTSTATUS
DgSnaFdoStopDevice(
	PPORT_EXT				pFdoExt, 
	PIRP					pIrp, 
	PIO_STACK_LOCATION		pIos
	)
/*++

Description:

	Handle an IRP_MN_STOP_DEVICE Irp.  The PnP Manager sends this IRP to stop a device
	so it can reconfigure its hardware resources. The PnP Manager only sends this IRP 
	if a prior IRP_MN_QUERY_STOP_DEVICE completed successfully.

	The PnP Manager sends this IRP at IRQL PASSIVE_LEVEL in the context of a system 
	thread.

Parameters:

	pExt
		Device extension for our FDO which describes the adapter.
	
	pIrp
		Request.  May be NULL, if called from within IRP_MN_SET_POWER to hibernate.

	pIos
		Parameters passed along with Irp.  May be NULL, if called from within 
		IRP_MN_SET_POWER to hibernate.

Return Value:

	This routine must return STATUS_SUCCESS.

--*/
{

	DigiDump( DIGIPNP, ( "DIGISNA!DgSnaFdoStopDevice: pFdoExt 0x%x , pSdoExt 0x%x\n", pFdoExt, pFdoExt->pSdo ) );

	if ( pFdoExt->PnpState == DIGIPNP_STARTED )
	{
		KIRQL	Irql;

		pFdoExt->PnpState = DIGIPNP_ADDED;

		if (pFdoExt->pSdo) 
		{

			PPORT_EXT	pSdoExt = pFdoExt->pSdo->DeviceExtension;

			KeAcquireSpinLock(&pSdoExt->ownership, &Irql);
			pSdoExt->state = Stopped;
			KeReleaseSpinLock(&pSdoExt->ownership,Irql);
		}
	}
	else
	{
		DigiDump( DIGIPNP, ( "DIGISNA!DgSnaFdoStopDevice: *** Bad PnpState 0x%x! ***\n", pFdoExt->PnpState ) );
	}

	if ( pIrp )
	{
		return DigiPnpForwardIrp( pFdoExt->pNextDeviceObject, pIrp );
	}
	else
	{
		// SET_POWER calls this routine internally to "hibernate".  It passes NULL 
		// pIrp and pIos pointers.
		return STATUS_SUCCESS;
	}
}



NTSTATUS
DgSnaFdoCancelStopDevice(
	PPORT_EXT				pFdoExt, 
	PIRP					pIrp, 
	PIO_STACK_LOCATION		pIos
	)
/*++

Description:

	Handle an IRP_MN_CANCEL_REMOVE_DEVICE Irp.  The PnP Manager uses this IRP to 
	inform the drivers for a device that the device will not be removed.

	The PnP Manager sends this IRP at IRQL PASSIVE_LEVEL in the context of a system 
	thread.

Parameters:

	pExt
		Device extension for our FDO which describes the adapter.
	
	pIrp
		Request.

	pIos
		Parameters passed along with Irp.

Return Value:

	STATUS_SUCCESS, or appropriate error status.

--*/
{
	NTSTATUS	Status = STATUS_SUCCESS;
	KIRQL		Irql;

	DigiDump( DIGIPNP, ( "DIGISNA!DgSnaFdoCancelStopDevice: pFdoExt 0x%x , pSdoExt 0x%x\n", pFdoExt, pFdoExt->pSdo ) );

	pIrp->IoStatus.Status = STATUS_SUCCESS;
	Status = DigiPnpForwardIrpWait( pFdoExt->pNextDeviceObject, pIrp );
	if ( NT_SUCCESS( Status ) )
	{

		if (pFdoExt->pSdo)
		{

			PPORT_EXT	pSdoExt = pFdoExt->pSdo->DeviceExtension;

			// retrun to IDLE
			KeAcquireSpinLock(&pSdoExt->ownership, &Irql);
			pSdoExt->state =IdleState;
			KeReleaseSpinLock(&pSdoExt->ownership,Irql);
		}
	}
	return Status;
}


NTSTATUS
DgSnaFdoQueryDeviceRelations(
	PPORT_EXT				pFdoExt, 
	PIRP					pIrp, 
	PIO_STACK_LOCATION		pIos
	)
/*++

Description:

	Handle an IRP_MN_QUERY_DEVICE_RELATIONS Irp.  The PnP Manager sends this IRP to 
	gather information about devices with a relationship to the specified device.

	The PnP Manager queries a device's BusRelations (child devices) when the device is
	enumerated and at other times while the device is active, such as when a driver 
	calls IoInvalidateDeviceRelations to indicate that a child device has arrived or 
	departed. 

	The PnP Manager queries a device's RemovalRelations before it removes or ejects 
	the device and it queries for EjectionRelations before it ejects a device.

	The PnP Manager queries a device's PowerRelations when a component registers for 
	power notification on the specified device or one of its descendants.

	The PnP Manager queries a device's TargetDeviceRelation when a driver or user-mode
	application registers for PnP notification of an EventCategoryTargetDeviceChange 
	on the device. The PnP Manager queries for the device that is associated with a 
	particular file object. This is the only PnP IRP that has a valid file object 
	parameter.

	The PnP Manager sends this IRP at IRQL PASSIVE_LEVEL in an arbitrary thread 
	context.

Return Value:

	STATUS_SUCCESS, or appropriate error status.

--*/
{
	NTSTATUS Status = pIrp->IoStatus.Status;
	ULONG_PTR Information = pIrp->IoStatus.Information;
	DigiDump( DIGIPNP, ( "DIGISNA!DgSnaFdoQueryDeviceRelations: pFdoExt 0x%x, QueryType %s\n", pFdoExt,
	          pIos->Parameters.QueryDeviceRelations.Type == BusRelations ? "BusRelations" :
	          pIos->Parameters.QueryDeviceRelations.Type == EjectionRelations ? "EjectionRelations" :
	          pIos->Parameters.QueryDeviceRelations.Type == PowerRelations ? "PowerRelations" :
	          pIos->Parameters.QueryDeviceRelations.Type == RemovalRelations ? "RemovalRelations" :
	          pIos->Parameters.QueryDeviceRelations.Type == TargetDeviceRelation ? "TargetDeviceRelation" : "Unknown" ) );

	switch ( pIos->Parameters.QueryDeviceRelations.Type )
	{

		// All Queries go on to the Bus Driver.  The switch is coded for any future
		// responses to the query.
		case BusRelations:
		case PowerRelations:
		case RemovalRelations:
		case EjectionRelations:
		case TargetDeviceRelation:
		default:
			Status = DigiPnpForwardIrp( pFdoExt->pNextDeviceObject, pIrp );
			break;
	}
		
	return Status;
}



NTSTATUS
DgSnaFdoSurpriseRemoval(
	PPORT_EXT				pFdoExt, 
	PIRP					pIrp, 
	PIO_STACK_LOCATION		pIos
	)
/*++

Description:

	Handle an IRP_MN_SURPRISE_REMOVAL Irp.  The PnP Manager sends this IRP to notify 
	the drivers for a device that the device has been unexpectedly removed from the 
	machine and is no longer available for I/O. 

	The PnP Manager sends this IRP before notifying user-mode components. After this 
	IRP completes, the PnP Manager sends a GUID_TARGET_DEVICE_REMOVE_COMPLETE 
	notification to components that registered for such notification.

	The PnP Manager sends this IRP at IRQL PASSIVE_LEVEL in the context of a system 
	thread.

Parameters:

	pExt
		Device extension for our FDO which describes the adapter.
	
	pIrp
		Request.

	pIos
		Parameters passed along with Irp.

Return Value:

	This routine must return STATUS_SUCCESS.

--*/
{

	NTSTATUS		Status;

	DigiDump( DIGIPNP, ( "DIGISNA!DgSnaFdoSurpriseRemoval: pFdoExt 0x%x\n", pFdoExt ) );

	if ( pFdoExt->PnpState == DIGIPNP_REMOVED )
	{
		DigiDump( DIGIPNP, ( "*** WARNING: DIGISNA!DgSnaFdoSurpriseRemoval: Already removed this device, 0x%x!\n", pFdoExt->pDeviceObject  ) );
	}

	if ( pFdoExt->PnpState == DIGIPNP_STARTED )
	{
		pFdoExt->PnpState = DIGIPNP_REMOVED;
	}

	Status = DigiPnpForwardIrp( pFdoExt->pNextDeviceObject, pIrp );

	// Detach from the Device Stack
	IoDetachDevice(	pFdoExt->pNextDeviceObject );

	// Remove the FDO
	IoDeleteDevice( pFdoExt->pDeviceObject );

	return Status;

}


NTSTATUS
DgSnaDevicePnp(
	IN PDEVICE_OBJECT pDevice,
	IN PIRP           pIrp
)
/*++

Description:

	Dispatch routine for all adapter FDO related PNP requests.

Parameters:

	pExt
		Device extension for our FDO which describes the adapter.
	
	pIrp
		Request.

	pIos
		Parameters passed along with Irp.

Return Value:

	STATUS_SUCCESS, or some error value.

--*/
{
	PPORT_EXT			pFdoExt = (PPORT_EXT) pDevice->DeviceExtension;
	PIO_STACK_LOCATION	pIos	= IoGetCurrentIrpStackLocation( pIrp );
	ULONG				Minor	= pIos->MinorFunction;
	NTSTATUS			Status;

	DigiDump( DIGIPNP, ( "DIGISNA!DgSnaDevicePnp: Enter, %s, pFdoExt 0x%08x, pIrp 0x%08x\n", 
	          DigiGetPnpIrpName(Minor),
	          pFdoExt,
			  pIrp )
			);

	switch ( Minor )
	{
		case IRP_MN_START_DEVICE:			Status = DgSnaFdoStartDevice( pFdoExt, pIrp, pIos );			break;
		case IRP_MN_QUERY_REMOVE_DEVICE:	Status = DgSnaFdoQueryRemoveDevice( pFdoExt, pIrp, pIos );		break;
		case IRP_MN_REMOVE_DEVICE:			Status = DgSnaFdoRemoveDevice( pFdoExt, pIrp, pIos );			break;
		case IRP_MN_CANCEL_REMOVE_DEVICE:	Status = DgSnaFdoCancelRemoveDevice( pFdoExt, pIrp, pIos );		break;
		case IRP_MN_QUERY_STOP_DEVICE:		Status = DgSnaFdoQueryStopDevice( pFdoExt, pIrp, pIos );		break;
		case IRP_MN_STOP_DEVICE:			Status = DgSnaFdoStopDevice( pFdoExt, pIrp, pIos );				break;
		case IRP_MN_CANCEL_STOP_DEVICE:		Status = DgSnaFdoCancelStopDevice( pFdoExt, pIrp, pIos );		break;
		case IRP_MN_QUERY_DEVICE_RELATIONS:	Status = DgSnaFdoQueryDeviceRelations( pFdoExt, pIrp, pIos );	break;
		case IRP_MN_SURPRISE_REMOVAL:		Status = DgSnaFdoSurpriseRemoval( pFdoExt, pIrp, pIos );		break;

		default:
			DigiDump( DIGIPNP, ( "DIGISNA!DgSnaDevicePnp: Passing unhandled PNP Irp %s on to PDO!\n", DigiGetPnpIrpName( Minor ) ) );
			Status = DigiPnpForwardIrp( pFdoExt->pNextDeviceObject, pIrp );
			break;
	}

	if ( NT_ERROR( Status ) )
	{
		DigiDump( DIGIPNP, ( "DIGISNA!DgSnaDevicePnp: %s, returning failure status 0x%x\n", DigiGetPnpIrpName(Minor), Status ) );
	}
	return Status;
}
#endif
