/*++
*********************************************************************************
* 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 Name:

   Fdo.c

Abstract:

   This module contains DSTREAMS provider FDO Plug and Play IRP Processing 
   Functions.

Author:

   Jeff Romatoski

--*/

#include "Digilib.h"    // defines NT5_BUILD

#if NT5_BUILD
 
#include "pnp.h"

///////////////////////////////////////////////////////////////////////////////////////
//
//  FDO Function Prototypes
//
///////////////////////////////////////////////////////////////////////////////////////

STATIC NTSTATUS
PnpFdoEnumerateChildPdoDevices(
	IN	PPNP_FDO_EXTENSION	pfdoExtension, 
	IN	HANDLE				hkParameters
	);

STATIC NTSTATUS
PnpFdoQueryRemoveDevice(
	IN	PPNP_FDO_EXTENSION	pfdoExtension, 
	IN	PIRP				pIrp, 
	IN	PIO_STACK_LOCATION  pIos
	);

STATIC NTSTATUS
PnpFdoQueryStopDevice(
	IN	PPNP_FDO_EXTENSION	pfdoExtension, 
	IN	PIRP				pIrp, 
	IN	PIO_STACK_LOCATION  pIos
	);

STATIC NTSTATUS
PnpFdoQueryDeviceRelations(
	IN	PPNP_FDO_EXTENSION	pfdoExtension, 
	IN	PIRP				pIrp, 
	IN	PIO_STACK_LOCATION  pIos
	);

STATIC NTSTATUS
PnpFdoRemoveDevice(
	IN	PPNP_FDO_EXTENSION	pfdoExtension, 
	IN	PIRP				pIrp, 
	IN	PIO_STACK_LOCATION  pIos
	);

STATIC NTSTATUS
PnpFdoCancelRemoveDevice(
	IN	PPNP_FDO_EXTENSION	pfdoExtension, 
	IN	PIRP                pIrp, 
	IN	PIO_STACK_LOCATION  pIos
	);

STATIC NTSTATUS
PnpFdoCancelStopDevice(
	IN	PPNP_FDO_EXTENSION	pfdoExtension, 
	IN	PIRP				pIrp, 
	IN	PIO_STACK_LOCATION  pIos
	);

STATIC NTSTATUS
PnpFdoSurpriseRemoval(
	IN	PPNP_FDO_EXTENSION	pfdoExtension, 
	IN	PIRP				pIrp, 
	IN	PIO_STACK_LOCATION  pIos
	);

VOID
PnpFdoDeleteChildList(IN	PPNP_FDO_EXTENSION	pfdoExtension
	);

STATIC NTSTATUS
PnpFdoBuildOpenLineKey( 
	OUT PHANDLE phKey,
	IN HANDLE   hKeyParent,
	IN UCHAR    iPort
	);


/////////////////////////////////////////////////////////////////////////////////////


STATIC NTSTATUS
PnpFdoBuildOpenEnumeratedKey( 
	OUT PHANDLE phKey,
	IN HANDLE   hKeyParent,
	IN PCWSTR   pRoot,
	IN UCHAR    iEnumStart,
	IN UCHAR    iPort )
/*++

Description:

	Open the numbered subkey (zero-based).  Port/Channel key names may not actually 
	use a numeric index, i.e. format may be PortA, PortB, etc.  This routine hides the 
	details and opens the appropriate key for the numbered port.

Parameters:

	phKey
		Handle to newly opened subkey is written here if successfully opened.

	hKeyParent
		Handle to opened registry key containing the enumerated keys

	pRoot
		String defining the Root of the subkey name, i.e. "Port", for PortA

	iPort
		Zero-based enum number whose key should be opened.

	iEnumStart
		Defines the starting Enum in the subkey name, i.e. 'A' in PortA

Return Value:

	STATUS_SUCCESS or failure status.

--*/
{
	NTSTATUS Status;
	UNICODE_STRING KeyName = {0};

	// Build the registry key name by appending (iPort+EnumStart) to Root.
	{
		USHORT Enum[2] = {(USHORT)(iPort+iEnumStart), 0};
		Status = DigiStrAllocUnicodeString( &KeyName, pRoot, Enum, NULL );
	}

	if ( NT_SUCCESS( Status ) )
	{
		HANDLE hKey;
		Status = DigiRegOpenSubkey( &hKey, hKeyParent, KeyName.Buffer );
		if ( NT_SUCCESS( Status ) )
			*phKey = hKey;
	}

	if ( KeyName.Buffer != NULL )
		DigiStrFreeUnicodeString( &KeyName );

	return Status;
}


STATIC NTSTATUS
PnpFdoBuildOpenLineKey( 
	OUT PHANDLE phKey,
	IN HANDLE   hKeyParent,
	IN UCHAR    iPort
	)
{
	return PnpFdoBuildOpenEnumeratedKey( phKey, hKeyParent, DXB_REGKEY_LINE_BASE,
										 DXB_REGKEY_LINE_ENUM_FIRST, iPort );
}


NTSTATUS
PnpFdoDispatch(
	PPNP_FDO_EXTENSION	pfdoExtension, 
	PIRP                pIrp
	)


/*++

Description:

	Dispatch routine for all adapter FDO related PNP requests.

Parameters:

	pfdoExtension
		FDO extension for our FDO device which describes the adapter.
	
	pIrp
		Request.

	pIos
		Parameters passed along with Irp.

Return Value:

	STATUS_SUCCESS, or some error value.

--*/
{
	PIO_STACK_LOCATION	pIos	= IoGetCurrentIrpStackLocation( pIrp );
	ULONG const			Minor	= pIos->MinorFunction;
	NTSTATUS			Status;

	DigiDump( DEBUG_DIGIPNP, ( "DsPnp!PnpFdoDispatch: %s, pfdoExtension 0x%08x, pIrp 0x%08x\n",
												DigiGetPnpIrpName(Minor),
												pfdoExtension,
												pIrp ) );

	switch ( Minor )
	{
		case IRP_MN_START_DEVICE:					Status = PnpFdoStartDevice( pfdoExtension, pIrp, pIos );	break;
		case IRP_MN_QUERY_REMOVE_DEVICE:			Status = PnpFdoQueryRemoveDevice( pfdoExtension, pIrp, pIos );	break;
		case IRP_MN_REMOVE_DEVICE:					Status = PnpFdoRemoveDevice( pfdoExtension, pIrp, pIos );	break;
		case IRP_MN_CANCEL_REMOVE_DEVICE:			Status = PnpFdoCancelRemoveDevice( pfdoExtension, pIrp, pIos );	break;
		case IRP_MN_QUERY_STOP_DEVICE:				Status = PnpFdoQueryStopDevice( pfdoExtension, pIrp, pIos );	break;
		case IRP_MN_STOP_DEVICE:					Status = PnpFdoStopDevice( pfdoExtension, pIrp, pIos );	break;
		case IRP_MN_CANCEL_STOP_DEVICE:				Status = PnpFdoCancelStopDevice( pfdoExtension, pIrp, pIos );	break;
		case IRP_MN_QUERY_DEVICE_RELATIONS:			Status = PnpFdoQueryDeviceRelations( pfdoExtension, pIrp, pIos );	break;
//		case IRP_MN_QUERY_INTERFACE:				Status = PnpFdoSkipAndForwardIrp( pfdoExtension, pIrp, pIos );	break;
//		case IRP_MN_QUERY_CAPABILITIES:				Status = PnpFdoSkipAndForwardIrp( pfdoExtension, pIrp, pIos );	break;
//		case IRP_MN_QUERY_RESOURCES:				Status = PnpFdoQueryResources( pfdoExtension, pIrp, pIos );	break;
//		case IRP_MN_QUERY_RESOURCE_REQUIREMENTS:	Status = PnpFdoQueryResourceRequirements( pfdoExtension, pIrp, pIos );	break;
//		case IRP_MN_QUERY_DEVICE_TEXT:				Status = PnpFdoQueryDeviceText( pfdoExtension, pIrp, pIos );	break;
//		case IRP_MN_FILTER_RESOURCE_REQUIREMENTS:	Status = PnpFdoSkipAndForwardIrp( pfdoExtension, pIrp, pIos );	break;
//		case IRP_MN_READ_CONFIG:					Status = PnpFdoReadConfig( pfdoExtension, pIrp, pIos );	break;
//		case IRP_MN_WRITE_CONFIG:					Status = PnpFdoWriteConfig( pfdoExtension, pIrp, pIos );	break;
//		case IRP_MN_EJECT:							Status = PnpFdoEject( pfdoExtension, pIrp, pIos );	break;
//		case IRP_MN_SET_LOCK:						Status = PnpFdoSetLock( pfdoExtension, pIrp, pIos );	break;
//		case IRP_MN_QUERY_ID:						Status = PnpFdoQueryId( pfdoExtension, pIrp, pIos );	break;
//		case IRP_MN_QUERY_PNP_DEVICE_STATE:			Status = PnpFdoSkipAndForwardIrp( pfdoExtension, pIrp, pIos );	break;
//		case IRP_MN_QUERY_BUS_INFORMATION:			Status = PnpFdoSkipAndForwardIrp( pfdoExtension, pIrp, pIos );	break;
//		case IRP_MN_DEVICE_USAGE_NOTIFICATION:		Status = PnpFdoDeviceUsageNotification( pfdoExtension, pIrp, pIos );	break;
		case IRP_MN_SURPRISE_REMOVAL:				Status = PnpFdoSurpriseRemoval( pfdoExtension, pIrp, pIos );	break;
//		case IRP_MN_QUERY_LEGACY_BUS_INFORMATION:	break;


		default:
			DigiDump( DEBUG_DIGIPNP, ( "DsPnp!PnpFdoDispatch: Passing unhandled PNP Irp %s on to PDO!\n", DigiGetPnpIrpName( Minor ) ) );
			Status = DigiPnpForwardIrp( pfdoExtension->pNextDeviceObject, pIrp );
			break;
	}

	if ( NT_ERROR( Status ) )
	{
		DigiDump( DEBUG_DIGIPNP, ( "DsPnp!PnpFdoDispatch: %s, returning failure status 0x%x\n", DigiGetPnpIrpName(Minor), Status ) );
	}
	return Status;
}


NTSTATUS
PnpFdoStartDevice(
	IN	PPNP_FDO_EXTENSION	pfdoExtension, 
	IN	PIRP				pIrp, 
	IN	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:

	pfdoExtension
		FDO extension for our FDO device 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_UNSUCCESSFUL;
	HANDLE					hkDriver			= NULL;
	HANDLE					hkParameters		= NULL;
	DIGI_RESOURCE_LIST		Resources;
	UNICODE_STRING			Option				= {0};
	BOOLEAN					fCalledInternally	= pIrp == NULL ? TRUE : FALSE;

	DigiDump( DEBUG_DIGIPNP, ( "DsPnp!PnpFdoStartDevice: pfdoExtension 0x%x\n", pfdoExtension ) );

	ASSERT (pfdoExtension->DeviceType == PNP_DEVICE_TYPE_FDO)
	
	if ( pfdoExtension->PnpState != PNP_ADDED )
	{
		DigiDump( DEBUG_DIGIPNP, ( "DsPnp!PnpFdoStartDevice: *** Bad PnpState 0x%x! ***\n", pfdoExtension->PnpState ) );
		goto Exit;
	}

	if ( !fCalledInternally )
	{
		Status = DigiPnpForwardIrpWait( pfdoExtension->pNextDeviceObject, pIrp );

		if ( NT_ERROR( Status ) )
			goto Exit;

#if DBG
		DigiDump( DEBUG_DIGIPNP, ( "DsPnp!PnpFdoStartDevice: AllocatedResources\n" ) );
		DigiDumpResourceList( pIos->Parameters.StartDevice.AllocatedResources );
		DigiDump( DEBUG_DIGIPNP, ( "DsPnp!PnpFdoStartDevice: AllocatedResourcesTranslated\n" ) );
		DigiDumpResourceList( pIos->Parameters.StartDevice.AllocatedResourcesTranslated );
#endif

		Status = DigiAssignPnpResources( pIos->Parameters.StartDevice.AllocatedResources,
		                                 pIos->Parameters.StartDevice.AllocatedResourcesTranslated,
		                                 &Resources );
		if ( NT_ERROR( Status ) )
		{
			DigiDump( DEBUG_DIGIPNP, ( "DsPnp!PnpFdoStartDevice: Error parsing resources lists, status 0x%x!\n", Status ) );
			goto Exit;
		}

		pfdoExtension->PowerResources = Resources;	// set backup copy of resources
	}
	else
	{
		Resources = pfdoExtension->PowerResources;	// use backup copy of resources
	}

	// Open adapter registry key, i.e. CurrentControlSet\Class\{...}\0001.
	Status = IoOpenDeviceRegistryKey( pfdoExtension->pPdo, PLUGPLAY_REGKEY_DRIVER, MAXIMUM_ALLOWED, &hkDriver );
	if ( NT_ERROR( Status ) )
	{
		DigiDump( DEBUG_DIGIPNP, ( "DsPnp!PnpFdoStartDevice: Error opening registry key, status 0x%x!\n", Status ) );
		goto Exit;
	}

	if ( NT_SUCCESS( Status ) )
		Status = DigiRegOpenSubkey( &hkParameters, hkDriver, DXB_REGKEY_PARAMETERS );

	if ( NT_SUCCESS( Status ) )
	{
		// First time a card comes up, the INF sets Spoof to TRUE, telling us to
		// spoof startup.  Skip configuration and clear the spoof flag so next time
		// we come up completely.
		ULONG Spoof;
		DigiRegQueryDword( hkParameters, DXB_REGVAL_SPOOF, &Spoof, DigiRegFlagOptional, FALSE );
		DigiRegWriteDword( hkParameters, DXB_REGVAL_SPOOF, 0 );
		pfdoExtension->fSpoof = Spoof != FALSE;
		if ( Spoof )
			goto EnumerateChildren;
	}		

#if DBG
	DigiGetDeviceProperties(pfdoExtension->pPdo);
#endif

	if ( NT_SUCCESS( Status ) )
		Status = DigiRegQuerySz( hkParameters, DXB_REGVAL_OPTION, &Option, DigiRegFlagRequired, NULL );

	if ( NT_SUCCESS( Status ) )
	{
		if (gDriverData.InitData.FdoData.CallbackStartDevice)
		{
			Status = gDriverData.InitData.FdoData.CallbackStartDevice(pfdoExtension->pDriverFdo,
																&Resources,
																hkDriver,
																hkParameters,
																&Option
																);
		}
	}

	if ( NT_SUCCESS( Status ) )
	{
		// This adapter is Good To Go!
		pfdoExtension->PnpState = PNP_STARTED;
	}

EnumerateChildren:
	if ( NT_SUCCESS( Status ) )
		(void)PnpFdoEnumerateChildPdoDevices( pfdoExtension, hkParameters );

Exit:
	if ( Option.Buffer )
		DigiRegFreeString( &Option );

	if ( hkParameters )
		DigiRegCloseKey( hkParameters );

	if ( hkDriver )
		ZwClose( hkDriver );

	if ( !fCalledInternally )
		DsPnpIoCompleteRequest( pIrp, Status, 0 );
	return Status;
}



STATIC NTSTATUS
PnpFdoEnumerateServiceDevices( 
	IN	PPNP_FDO_EXTENSION	pfdoExtension, 
	HANDLE					hkParameters,
	PUNICODE_STRING			pParamsKey,
	PUNICODE_STRING			pServiceName,
	ULONG					iLine,
	ULONG					iService
)
/*++

Description:

	Walk keys under the specified service and line, enumerating child devices.  Child
	devices now come in two flavors:  one-per-line, and multiple-per-line.  Single 
	child per line devices include NDIS WAN miniports and SNA.  Multiple child per line
	devices include Asyn ports, NDIS LAN miniports (both FR and X25).

	Single child-per-line devices are located in keys such as

		\...\Parameters\ISDN\0

	Multiple child-per-line devices are located in keys such as

		\...\Parameters\ISDN\0



Parameters:

	pfdoExtension
		Adapter FDO device extension

	hkParameters
		Handle to previously opened Parameters adapter subkey.

	pParamsKey
		String defining the name of the adapter parameters key from netclass guid down,
		e.g. "{...}\0008\Parameters".

	ServiceName
		Service subkey name, e.g. "ISDN".  Should be a DXB_REGVAL_LINE_SERVICE_* value.

	iLine
		Zero-based line number on this adapter.  Appended to the ServiceName to define 
		the service instance key.  Some services will have child devices here (Wan 
		drivers - ISDN, X25, etc),	others define children in the keys one level lower 
		(Asyn and Lan drivers). 
		
	iService
		Zero-based entry in the LineServices value for this line (identified by iLine).
		Used along with iLine to create unique identifiers for each child device.

Return Value:

	STATUS_SUCCESS or error.

--*/
{
	NTSTATUS Status;
	UNICODE_STRING ServiceKey = {0};
	UNICODE_STRING LineNumber;
	WCHAR buf[64];
	LineNumber.Buffer = buf;
	LineNumber.Length = 0;
	LineNumber.MaximumLength = sizeof buf;
	Status = RtlIntegerToUnicodeString( iLine, 10, &LineNumber );

	// Create Parameters subkey name, e.g. ISDN\0.  We'll open this subkey.
	if ( NT_SUCCESS(Status) )
		Status = DigiStrAllocUnicodeString( &ServiceKey, pServiceName->Buffer, L"\\", LineNumber.Buffer );
	
	if ( NT_SUCCESS(Status) )
	{
		HANDLE hkService = NULL;
		Status = DigiRegOpenSubkey( &hkService, hkParameters, ServiceKey.Buffer );
		if ( NT_SUCCESS(Status) )
		{
			// Read HardwareId.  If not present, enumerate subkeys and look for children 
			// there...
			UNICODE_STRING HardwareId = {0};

			Status = DigiRegQuerySz( hkService, DXB_REGVAL_PDO_HDWID, &HardwareId, DigiRegFlagOptional, L"" );
			if ( NT_SUCCESS(Status) && HardwareId.Length )
			{
				// Found it.  Single child-per-line.  Create the child.
				if ( NT_SUCCESS(Status) )
					Status = PnpPdoCreateDevice( pfdoExtension, hkService, pParamsKey, &ServiceKey, NULL, iService, iLine, &HardwareId );
			}
			else
			{
				// Must be multiple children-per-line.  Each child will be represented by
				// a subkey.  Enumerate subkeys and create children.
				ULONG i;
				UNICODE_STRING KeyName;
				UNICODE_STRING SubHardwareId;
				for ( i = 0; NT_SUCCESS(DigiRegEnumSubkey(hkService, i, &KeyName)); i++ )
				{
					HANDLE hkSub = NULL;
					Status = DigiRegOpenSubkey( &hkSub, hkService, KeyName.Buffer );

					if ( NT_SUCCESS(Status) )
						Status = DigiRegQuerySz( hkSub, DXB_REGVAL_PDO_HDWID, &SubHardwareId, DigiRegFlagOptional, L"" );

					if ( NT_SUCCESS(Status) )
					{
						if ( SubHardwareId.Length )
						{
							// Found it.  Create child.
							UNICODE_STRING ServiceSubkey = {0};
							Status = DigiStrAllocUnicodeString( &ServiceSubkey, ServiceKey.Buffer, L"\\", KeyName.Buffer );
							if ( NT_SUCCESS(Status) )
							{
								Status = PnpPdoCreateDevice( pfdoExtension, hkSub, pParamsKey, &ServiceSubkey, &KeyName, iService, iLine, &SubHardwareId );
								DigiStrFreeUnicodeString( &ServiceSubkey );
							}
						}
						DigiRegFreeString( &SubHardwareId );
					}
					DigiRegCloseKey( hkSub );
					DigiRegFreeString( &KeyName );
				}

				Status = STATUS_SUCCESS;
			}

			if ( HardwareId.Buffer )
				DigiRegFreeString( &HardwareId );

			DigiRegCloseKey( hkService );
		}
		else
		{
			DigiDump( DEBUG_DIGIPNP, ( "DsPnp!PnpFdoEnumerateServiceDevices: Error opening Service key, %S\n", ServiceKey.Buffer ) );
		}
	}

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

	return Status;
}



STATIC NTSTATUS
PnpFdoEnumerateChildPdoDevices(
	IN	PPNP_FDO_EXTENSION	pfdoExtension, 
	IN	HANDLE				hkParameters
	)
/*++

Description:

	Create child device PDOs.  Walk registry key to determine number and type of child 
	devices to create.

Parameters:

	pfdoExtension
		FDO extension for our FDO device which describes the adapter.

	hkParameters
		Handle to previously opened registry key, i.e. Control/Class/{...}/0001/Parameters.

Return Value:

	STATUS_SUCCESS or error.

--*/
{
	NTSTATUS Status;
	WCHAR buf[128];
	ULONG cb;
	UNICODE_STRING ParamsKey = {0};
	ULONG nLines = 0;

	DigiDump( DEBUG_DIGIPNP, ( "DsPnp!PnpFdoEnumerateChildPdoDevices: Enter, pfdoExtension %08x\n", pfdoExtension ) );

	// Get driver keyname.  We'll use this to create a full path to each subkey that
	// represents a child device.  This path is written to the PDOs key, so the client
	// driver has a link back to here, to access config data for the client.
	Status = IoGetDeviceProperty( pfdoExtension->pPdo, DevicePropertyDriverKeyName, sizeof buf, buf, &cb );
	if ( NT_SUCCESS(Status) )
	{
		RtlInitUnicodeString( &ParamsKey, buf );
		ParamsKey.MaximumLength = sizeof buf;
		RtlAppendUnicodeToString( &ParamsKey, L"\\Parameters" );
	}

	if ( NT_SUCCESS(Status) )
		Status = DigiRegQueryDword( hkParameters, DXB_REGVAL_NUMBEROFLINES, &nLines, DigiRegFlagRequired, 0 );

	if ( NT_SUCCESS(Status) )
	{
		UCHAR iLine;

		// Find children on each line
		for ( iLine = 0; iLine < nLines; iLine++ )
		{
			UNICODE_STRING LineServices = {0};
			HANDLE hkLine = NULL;

			Status = PnpFdoBuildOpenLineKey( &hkLine, hkParameters, iLine );
			if ( NT_SUCCESS(Status) )
			{
				Status = DigiRegQueryMultiSz( hkLine, DXB_REGVAL_LINE_SERVICES, &LineServices, DigiRegFlagRequired, 0 );
				DigiRegCloseKey( hkLine );
			}

			// Children exist as instances of services on a given line.  So, walk the list
			// of active services (e.g. client drivers) for each line, looking for child
			// devices.  

			if ( NT_SUCCESS(Status) )
			{
				ULONG iService;
				UNICODE_STRING ServiceName;

				for ( iService = 0; NT_SUCCESS(Status) && DigiStrExtractMultiSzEntry( &ServiceName, &LineServices, iService ); iService++ )
					Status = PnpFdoEnumerateServiceDevices( pfdoExtension, hkParameters, &ParamsKey, &ServiceName, iLine, iService );
			}

			if ( LineServices.Buffer )
				DigiRegFreeString( &LineServices );
		}
	}

	DigiDump( DEBUG_DIGIPNP, ( "DsPnp!PnpFdoEnumerateChildPdoDevices: Leave, pfdoExtension %08x\n", pfdoExtension ) );

	return STATUS_SUCCESS;

}



STATIC NTSTATUS
PnpFdoQueryRemoveDevice(
	IN	PPNP_FDO_EXTENSION	pfdoExtension, 
	IN	PIRP				pIrp, 
	IN	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:

	pfdoExtension
		FDO extension for our FDO device which describes the adapter.
	
	pIrp
		Request.

	pIos
		Parameters passed along with Irp.

Return Value:

	STATUS_SUCCESS, or appropriate error status.

--*/
{
	NTSTATUS Status;
	DigiDump( DEBUG_DIGIPNP, ( "DsPnp!PnpFdoQueryRemoveDevice: pfdoExtension 0x%x\n", pfdoExtension ) );

	Status = STATUS_SUCCESS;

	// Call back
	if (gDriverData.InitData.FdoData.CallbackQueryRemoveDevice)
	{
		Status = gDriverData.InitData.FdoData.CallbackQueryRemoveDevice(pfdoExtension->pDriverFdo);
	}

	if ( NT_SUCCESS( Status ) )
	{
		// We can Remove it, continue
		pIrp->IoStatus.Status = Status;
		Status = DigiPnpForwardIrp( pfdoExtension->pNextDeviceObject, pIrp );
	}
	else
	{
		// We can't Remove it, reject the request now
		DsPnpIoCompleteRequest( pIrp, Status, 0 );
	}

	return Status;
}



STATIC NTSTATUS
PnpFdoRemoveDevice(
	IN	PPNP_FDO_EXTENSION	pfdoExtension, 
	IN	PIRP				pIrp, 
	IN	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:

	pfdoExtension
		FDO extension for our FDO device which describes the adapter.
	
	pIrp
		Request.

	pIos
		Parameters passed along with Irp.

Return Value:

	This routine must return STATUS_SUCCESS.

--*/
{
	PDEVICE_OBJECT const pNext = pfdoExtension->pNextDeviceObject;

	DigiDump( DEBUG_DIGIPNP, ( "DsPnp!PnpFdoRemoveDevice: pfdoExtension 0x%x\n", pfdoExtension ) );

	pIrp->IoStatus.Status = STATUS_SUCCESS;

	if ( pfdoExtension->PnpState == PNP_REMOVED )
	{
		DigiDump( DEBUG_DIGIPNP, ( "*** WARNING: DsPnp!PnpFdoRemoveDevice: Got a second Remove on this device, 0x%x!\n", pfdoExtension  ) );
	}

	if ( pfdoExtension->PnpState == PNP_STARTED )
	{
		pfdoExtension->PnpState = PNP_REMOVED;

		// Delete the Child Devices
		PnpFdoDeleteChildList( pfdoExtension );
	}

   // Remove our device from the Device Tree
   if ( pfdoExtension->pNextDeviceObject )
   {
	   IoDetachDevice( pfdoExtension->pNextDeviceObject );
   }

   // Call back
   if (gDriverData.InitData.FdoData.CallbackRemoveDevice)
   {
	   gDriverData.InitData.FdoData.CallbackRemoveDevice(pfdoExtension->pDriverFdo);
   }

	// Get Rid of the FDO Extenion
	DigiFreeMem(pfdoExtension);

	return DigiPnpForwardIrp( pNext, pIrp );
}

STATIC NTSTATUS
PnpFdoCancelRemoveDevice(
	IN	PPNP_FDO_EXTENSION	pfdoExtension, 
	IN	PIRP                pIrp, 
	IN	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:

	pfdoExtension
		FDO extension for our FDO device which describes the adapter.
	
	pIrp
		Request.

	pIos
		Parameters passed along with Irp.

Return Value:

	STATUS_SUCCESS, or appropriate error status.

--*/
{
	NTSTATUS Status = STATUS_SUCCESS;

	DigiDump( DEBUG_DIGIPNP, ( "DsPnp!PnpFdoCancelRemoveDevice: pfdoExtension 0x%x\n", pfdoExtension ) );

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

	DsPnpIoCompleteRequest( pIrp, Status, 0 );
	return Status;

}


STATIC NTSTATUS
PnpFdoQueryStopDevice(
	IN	PPNP_FDO_EXTENSION	pfdoExtension, 
	IN	PIRP				pIrp, 
	IN	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:

	pfdoExtension
		FDO extension for our FDO device which describes the adapter.
	
	pIrp
		Request.

	pIos
		Parameters passed along with Irp.

Return Value:

	STATUS_SUCCESS, or appropriate error status.

--*/
{
	NTSTATUS Status = STATUS_SUCCESS;
	DigiDump( DEBUG_DIGIPNP, ( "DsPnp!PnpFdoQueryStopDevice: pfdoExtension 0x%x\n", pfdoExtension ) );

	Status = STATUS_SUCCESS;

	if ( NT_SUCCESS( Status ) )
	{
		// We can stop it, continue
		pIrp->IoStatus.Status = Status;
		Status = DigiPnpForwardIrp( pfdoExtension->pNextDeviceObject, pIrp );
	}
	else
		// We can't stop it, reject the request now
		DsPnpIoCompleteRequest( pIrp, Status, 0 );

	return Status;
}



NTSTATUS
PnpFdoStopDevice(
	IN	PPNP_FDO_EXTENSION	pfdoExtension, 
	IN	PIRP				pIrp, 
	IN	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:

	pfdoExtension
		FDO extension for our FDO device 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( DEBUG_DIGIPNP, ( "DsPnp!PnpFdoStopDevice: pfdoExtension 0x%x\n", pfdoExtension ) );

	if ( pfdoExtension->PnpState == PNP_STARTED )
	{
		// Delete the Child Devices
		PnpFdoDeleteChildList( pfdoExtension );

		// Call back
		if (gDriverData.InitData.FdoData.CallbackStopDevice)
		{
			gDriverData.InitData.FdoData.CallbackStopDevice(pfdoExtension->pDriverFdo);
		}

		pfdoExtension->PnpState = PNP_ADDED;
	}
	else
	{
		DigiDump( DEBUG_DIGIPNP, ( "DsPnp!PnpFdoStopDevice: *** Bad PnpState 0x%x! ***\n", pfdoExtension->PnpState ) );
	}

	if ( pIrp )
	{
		// We're moving from PNP_STARTED to PNP_ADDED, child devices need to be removed.
		// Tell PNP to re-query our device relations, at which time we tell him our
		// children have gone away.  PNP subsequently REMOVEs our child devices.
		IoInvalidateDeviceRelations( pfdoExtension->pDriverFdo, BusRelations );
		return DigiPnpForwardIrp( pfdoExtension->pNextDeviceObject, pIrp );
	}
	else
	{
		// SET_POWER calls this routine internally to "hibernate".  It passes NULL 
		// pIrp and pIos pointers.
		return STATUS_SUCCESS;
	}
}


STATIC NTSTATUS
PnpFdoCancelStopDevice(
	IN	PPNP_FDO_EXTENSION	pfdoExtension, 
	IN	PIRP				pIrp, 
	IN	PIO_STACK_LOCATION  pIos
	)

/*++

Description:

	Handle an IRP_MN_CANCEL_STOP_DEVICE Irp.  The PnP Manager uses this IRP to inform 
	the drivers for a device that the device will not be stopped for resource 
	reconfiguration.
	
	The PnP Manager sends this IRP at IRQL PASSIVE_LEVEL in the context of a system 
	thread.

Parameters:

	pfdoExtension
		FDO extension for our FDO device which describes the adapter.
	
	pIrp
		Request.

	pIos
		Parameters passed along with Irp.

Return Value:

	STATUS_SUCCESS, or appropriate error status.

--*/
{
	NTSTATUS Status = STATUS_SUCCESS;

	DigiDump( DEBUG_DIGIPNP, ( "DsPnp!PnpFdoCancelStopDevice: pfdoExtension 0x%x\n", pfdoExtension ) );

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

	DsPnpIoCompleteRequest( pIrp, Status, 0 );
	return Status;
}


NTSTATUS
STATIC PnpFdoQueryDeviceRelations(
	IN	PPNP_FDO_EXTENSION	pfdoExtension, 
	IN	PIRP				pIrp, 
	IN	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;
	DigiDump( DEBUG_DIGIPNP, ( "DsPnp!PnpFdoQueryDeviceRelations: pfdoExtension 0x%x, QueryType %s\n", pfdoExtension,
	          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 )
	{
		case BusRelations:
		{
			ULONG nChildren = 0;

			// Count children to determine size of struct to allocate.  If not running,
			// use default nChildren=0.
			if ( pfdoExtension->PnpState == PNP_STARTED )
			{
				PLIST_ENTRY pEntry = pfdoExtension->ChildListHead.Flink;
				while ( pEntry != &pfdoExtension->ChildListHead )
				{
					nChildren++;
					pEntry = pEntry->Flink;
				}
			}

			// Create response.  Alloc from paged pool, PNP manager will later free it!
			{
				PDEVICE_RELATIONS pRelations = ExAllocatePool( PagedPool, sizeof *pRelations + nChildren * sizeof(PVOID) );
				if ( !pRelations )
				{
					pIrp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
					pIrp->IoStatus.Information = 0;
					break;
				}
				pRelations->Count = nChildren;
				DigiDump( DEBUG_DIGIPNP, ( "DsPnp!PnpFdoQueryDeviceRelations: returning %d %s:\n", pRelations->Count, pRelations->Count == 1 ? "child" : "children") );

				// Fill in array of child device object pointers
				{
					ULONG i;
					PLIST_ENTRY pEntry;
					for ( i = 0, pEntry = pfdoExtension->ChildListHead.Flink; 
					      i < nChildren && pEntry != &pfdoExtension->ChildListHead; 
					      i++, pEntry = pEntry->Flink )
					{
						PPNP_PDO_EXTENSION ppdoExtension = CONTAINING_RECORD( pEntry,
																			  PNP_PDO_EXTENSION,
																			  ChildListEntry );
						ASSERT( ppdoExtension->DeviceType == PNP_DEVICE_TYPE_PDO );
						DigiDump( DEBUG_DIGIPNP, ( "DsPnp!PnpFdoQueryDeviceRelations: 0x%08x\n", ppdoExtension ) );
						pRelations->Objects[i] = ppdoExtension->pDeviceObject;
						ObReferenceObject( ppdoExtension->pDeviceObject );	// PNP removes the reference
					}
				}
				if ( pIrp->IoStatus.Information != 0 )
				{
					DigiDump( DEBUG_DIGIPNP, ( "DsPnp!PnpQueryDeviceRelations: *** Freeing previous DEVICE_RELATIONS struct ***\n" ) );
					ExFreePool( (PVOID)pIrp->IoStatus.Information );
				}

				pIrp->IoStatus.Information = (ULONG_PTR)pRelations;
				pIrp->IoStatus.Status = STATUS_SUCCESS;
			}
			break;
		}

		case PowerRelations:
		case RemovalRelations:
		case EjectionRelations:			// fall through
		case TargetDeviceRelation:		// fall through
		default:
			break;
	}
		
	return DigiPnpForwardIrp( pfdoExtension->pNextDeviceObject, pIrp );;
}


STATIC NTSTATUS
PnpFdoSurpriseRemoval(
	IN	PPNP_FDO_EXTENSION	pfdoExtension, 
	IN	PIRP				pIrp, 
	IN	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:

	pfdoExtension
		FDO extension for our FDO device which describes the adapter.
	
	pIrp
		Request.

	pIos
		Parameters passed along with Irp.

Return Value:

	This routine must return STATUS_SUCCESS.

--*/
{

	PDEVICE_OBJECT const pNext = pfdoExtension->pNextDeviceObject;

	DigiDump( DEBUG_DIGIPNP, ( "DsPnp!PnpFdoSurpriseRemoval: pfdoExtension 0x%x\n", pfdoExtension ) );

	pIrp->IoStatus.Status = STATUS_SUCCESS;

	if ( pfdoExtension->PnpState == PNP_REMOVED )
	{
		DigiDump( DEBUG_DIGIPNP, ( "*** WARNING: DsPnp!PnpFdoSurpriseRemoval: Got a second Remove on this device, 0x%x!\n", pfdoExtension  ) );
	}

	if ( pfdoExtension->PnpState == PNP_STARTED )
	{
		pfdoExtension->PnpState = PNP_REMOVED;

		// Delete the Child Devices
		PnpFdoDeleteChildList( pfdoExtension );
   }

	// Remove our device from the Device Tree
	if ( pfdoExtension->pNextDeviceObject )
	{
		IoDetachDevice( pfdoExtension->pNextDeviceObject );
	}

	// Call back
	if (gDriverData.InitData.FdoData.CallbackRemoveDevice)
	{
		gDriverData.InitData.FdoData.CallbackRemoveDevice(pfdoExtension->pDriverFdo);
	}

	// Get Rid of the FDO Extenion
	DigiFreeMem(pfdoExtension);

	return DigiPnpForwardIrp( pNext, pIrp );}


NTSTATUS
PnpFdoCheckState(
	IN	PPNP_FDO_EXTENSION	pfdoExtension
	)
/*++

Description:

	Check the state of the FDO.

Parameters:

	pfdoExtension
		FDO extension for our FDO device which describes the adapter.
	

Return Value:

	STATUS_SUCCESS, or appropriate error status.

--*/
{

	NTSTATUS Status;

	// Fail create if spoofing!
	Status = pfdoExtension->fSpoof ? STATUS_UNSUCCESSFUL : STATUS_SUCCESS;

	// Fail create if adapter isn't started
	if ( NT_SUCCESS( Status ) )
		Status = pfdoExtension->PnpState == PNP_STARTED ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL;

	return Status;

}


VOID
PnpFdoDeleteChildList(IN	PPNP_FDO_EXTENSION	pfdoExtension
)
/*++

Description:

	Delete all child device objects for this FDO.

Parameters:

	pfdoExtension
		FDO device extension

Return Value:

	VOID

--*/
{
	PLIST_ENTRY pEntry;
	PLIST_ENTRY pHead = &pfdoExtension->ChildListHead;

	DigiAcquireSpinLock(&pfdoExtension->ChildListLock);
	pEntry = RemoveHeadList( &pfdoExtension->ChildListHead );
	DigiReleaseSpinLock(&pfdoExtension->ChildListLock);

	// Must not hold spinlock while deleting device objects!
	while ( pEntry != pHead )
	{
		PPNP_PDO_EXTENSION pPdo = CONTAINING_RECORD( pEntry, PNP_PDO_EXTENSION, ChildListEntry );
		ASSERT( pPdo->DeviceType == PNP_DEVICE_TYPE_PDO );
		PnpPdoDeleteDevice( pPdo );

		// Get next entry
		DigiAcquireSpinLock(&pfdoExtension->ChildListLock);
		pEntry = RemoveHeadList( &pfdoExtension->ChildListHead );
		DigiReleaseSpinLock(&pfdoExtension->ChildListLock);
	}
}

void
PnpFdoDeleteDevice(
	IN	PPNP_FDO_EXTENSION	pfdoExtension
	)
/*++

Description:

	Detach and Delete the FDO Extension.

Parameters:

	pfdoExtension
		FDO extension for our FDO device which describes the adapter.
	

Return Value:

	STATUS_SUCCESS, or appropriate error status.

--*/
{

	if ( pfdoExtension->pNextDeviceObject )
		IoDetachDevice( pfdoExtension->pNextDeviceObject );

	DigiFreeMem(pfdoExtension);

}

#endif // NT5_BUILD