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

   dxbchild.c

Abstract:

   NT 5.0 PNP Irp handler routines for child PDOs.

Author:

   Michael Zarns

--*/
#include "digidxb.h"
#include "stdio.h"

#if NT5_BUILD
#include <initguid.h>
#include <devguid.h>

STATIC VOID
dxbChildDeleteDevice(
	PDXB_CHILD_DEVICE pChild )
/*++

Description:

	Delete a single child device.

Parameters:

	pChild
		Device extension to delete

--*/
{
	ASSERT( pChild->Type == DXB_TYPE_CHILD_DEVICE );
	ASSERT( pChild->pPhysicalDeviceObject );
	DigiDump( DIGIPNPCHILD, ( "Dxb!ChildDeleteDevice: deleting child PDO 0x%p, ext 0x%p\n", pChild->pPhysicalDeviceObject, pChild ) );

	if ( pChild->HardwareId.Buffer )
		DigiStrFreeUnicodeString( &pChild->HardwareId );

	if ( pChild->RegPath.Buffer )
		DigiStrFreeUnicodeString( &pChild->RegPath );

	if ( pChild->InstanceId.Buffer )
		DigiStrFreeUnicodeString( &pChild->InstanceId );

	IoDeleteDevice( pChild->pPhysicalDeviceObject );
}


BOOLEAN ChildrenRemoved(IN PDXB_DEVICE_EXTENSION pExt)
{
    BOOLEAN found = FALSE;
    PLIST_ENTRY pEntry;
    PLIST_ENTRY pHead = &pExt->ChildListHead;
    DxbAdapterLock( pExt );
    pEntry = RemoveHeadList( &pExt->ChildListHead );
    DxbAdapterUnlock( pExt );

    // Must not hold spinlock while deleting device objects!
    while ( pEntry != pHead )
    {
        PDXB_CHILD_DEVICE pChild = CONTAINING_RECORD( pEntry, DXB_CHILD_DEVICE, ListEntry );
        ASSERT( pChild->Type == DXB_TYPE_CHILD_DEVICE );

        pChild->Removed = TRUE;
        found = TRUE;

        // Get next entry
        DxbAdapterLock( pExt );
        pEntry = RemoveHeadList( &pExt->ChildListHead );
        DxbAdapterUnlock( pExt );
    }

    return found;
}


STATIC NTSTATUS
dxbChildCreateDevice(
	PDXB_DEVICE_EXTENSION pExt,
	IN HANDLE             hkChild,
	IN PUNICODE_STRING    pParametersKey, 				// "\{GUID}\0008\Parameters"
	IN PUNICODE_STRING    pServiceKey, 					// "ISDN\0"
	IN PUNICODE_STRING    pServiceSubkey OPTIONAL,	// "0"
	IN ULONG              iService,
	IN ULONG              iLine,
	IN PUNICODE_STRING    pHardwareId )					// "DIGIBRI"
/*++

Description:

	Alloc and init struct that describes a dxb child device.  This descriptor stays
	for the life of an adapter (between StartDevice and StopDevice), while the
	device_object it owns may be deleted/added again and again.

Parameters:

	pExt
		Adapter device extension

	hkChild
		Handle to previously opened regkey containing the config params for the child
		device.  It is under dxb\Parameters and is where the HardwareId was found.  We
		write the NT devicename the client should open to talk to this adapter.

	pParametersKey
		Partial registry path from NetClass GUID down to our adapter Parameters key.
		This key, combined with pServiceKey define the full path (again from netclass
		down) to the child device.  We write this to the child device so it can find its
		way back here.  e.g. {4D36E972-E325-11CE-BFC1-08002BE10318}\0008\Parameters.

	pServiceKey
		String defining the subkey (under Parameters) defining this child device.  It
		is the key in which HardwareId was found, and is where all config params for
		the client driver are stored.  e.g. ISDN\0.

	pServiceSubkey
		String defining the instance number, if this device is a multiple-child-per-line
		device.  Otherwise, NULL.

	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.

	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).

	pHardwareId
		String defining hardware Id of the child device.  HardwareId's define what
		client driver is loaded to handle the device.

Return Value:

	STATUS_SUCCESS or error.

--*/
{
	NTSTATUS Status = STATUS_INSUFFICIENT_RESOURCES;
	PDEVICE_OBJECT pDevice = NULL;
	PDXB_CHILD_DEVICE pChild = NULL;
	ULONG iKeyName;

	// Create device object.  Note: this automatically zeros out the device extension.
	Status = IoCreateDevice( gpDriverObject, sizeof(DXB_CHILD_DEVICE), NULL, FILE_DEVICE_UNKNOWN, FILE_AUTOGENERATED_DEVICE_NAME, FALSE, &pDevice );
	if ( NT_ERROR(Status) )
		goto Exit;

	pChild = pDevice->DeviceExtension;
	pChild->Type = DXB_TYPE_CHILD_DEVICE;
	pChild->pPhysicalDeviceObject = pDevice;
	pChild->pParentExt = pExt;

    pChild->PowerState = PowerDeviceD0;
    pChild->Removed = FALSE;

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

	// Indicate that we require POWER IRPs to come in at PASSIVE_LEVEL rather than
	// at DISPATCH_LEVEL.  This must be consistent with all device object in the
	// stack -- meaning that our adapter FDO, it's PDO, and all child PDOs must be
	// the same.
	pDevice->Flags |= DO_POWER_PAGABLE;

	// HardwareId identifies the child device type, e.g. DIGIBRI or DIGIASYN.
	Status = DigiStrAllocUnicodeString( &pChild->HardwareId, pHardwareId->Buffer, NULL, NULL );

	// RegPath identifies the location of config params for this child.  We later write
	// it to the client drivers key.
	if ( NT_SUCCESS(Status) )
		Status = DigiStrAllocUnicodeString( &pChild->RegPath, pParametersKey->Buffer, L"\\", pServiceKey->Buffer );

	if ( NT_SUCCESS(Status) )
	{
		// ServiceSubkey, e.g. last key in {NetGUID}\0001\Parameters\ASYN\0\0, is a
		// one or two digit number.  Convert it to an int, later we'll sprintf it back
		// into a two digit string in the InstanceId.
		if ( pServiceSubkey )
			Status = RtlUnicodeStringToInteger( pServiceSubkey, 10, &iKeyName );
		else
		{
			Status = STATUS_SUCCESS;
			iKeyName = 0;
		}
	}

	if ( NT_SUCCESS(Status) )
	{
		// Create this child device instance id.  The Id must be unique for this
		// type of device on this adapter.  So, use two identifying numbers:
		//    a) the zero-based line number
		//    b) the arbitrary regkey name for this child's config key
		// For single child-per-line clients (e.g. ISDN) a and b are redundant.  For
		// multiple child-per-line clients (e.g. ASYN) a and b uniquely identify the
		// serial port.
		// For example, child identifiers might be:
		//
		//        Key              InstanceId
		// "Parameters/ISDN/0"     "0000"
		// "Parameters/ISDN/1"     "0101"
		// "Parameters/ASYN/0/0"   "0000"
		// "Parameters/ASYN/1/2"   "0102"
		//
		// Note: ASYN and ISDN devices may have the same instance id - that's OK.  The
		// only requirement is that the instance id be unique for a specific device type
		// on a single bus.

		WCHAR buf[10] = {0};
		swprintf( buf, L"%02i%02i", iLine, iKeyName );
		Status = DigiStrAllocUnicodeString( &pChild->InstanceId, buf, NULL, NULL );
	}

	// Write devicename used by client driver to open this adapter
	Status = DigiRegWriteSz( hkChild, DXB_REGVAL_DEVICE, pExt->Name.Buffer );

	if ( NT_ERROR( Status ) )
		goto Exit;

	{
		// Most of our child devices shouldn't be displayed in the Device Manager.
		// The exception is the serial ports attached to Xp adapters. Xp adapters are
		// installed as MultiPortSerial devices - so check the GUID of the adapter
		// to know how to set the DeviceState flag...

		NTSTATUS LocalStatus;
		PWCHAR	pGuidStr;
		ULONG		cb;
      
      // Default to not displaying the devices in the UI.
      pChild->DeviceStateFlags = PNP_DEVICE_DONT_DISPLAY_IN_UI;

		LocalStatus = IoGetDeviceProperty( pExt->pPdo, DevicePropertyClassGuid, 0, NULL, &cb );
		if ( LocalStatus == STATUS_BUFFER_TOO_SMALL && cb != 0)
		{
			pGuidStr = DigiAllocMem( NonPagedPool, cb );
			if ( NULL != pGuidStr )
			{
         	UNICODE_STRING	GuidUString;
            GUID				Guid;

				LocalStatus = IoGetDeviceProperty( pExt->pPdo, DevicePropertyClassGuid, cb,
																										pGuidStr, &cb );
            if (NT_SUCCESS( LocalStatus ))
					LocalStatus = DigiStrAllocUnicodeString( &GuidUString, pGuidStr, NULL, NULL );
            if (NT_SUCCESS( LocalStatus ))
					LocalStatus = RtlGUIDFromString( &GuidUString, &Guid );
            if (NT_SUCCESS( LocalStatus ) &&
								IsEqualGUID( &Guid, (PVOID)&GUID_DEVCLASS_MULTIPORTSERIAL ))
					// Parent is a MultiPortSerial device class
					//  - this child device should be displayed in device manager.
            	pChild->DeviceStateFlags = 0;

            DigiFreeMem( pGuidStr );
				if ( GuidUString.Buffer )
					DigiStrFreeUnicodeString( &GuidUString );
			}
		}
	}

	{
		DxbAdapterLock( pExt );
		InsertTailList( &pExt->ChildListHead, &pChild->ListEntry );
		DxbAdapterUnlock( pExt );

		DigiDump( DIGIPNPCHILD, ( "   PDO 0x%p, pExt 0x%p, %S, %S\n", pChild, pExt, pChild->HardwareId.Buffer, pChild->InstanceId.Buffer ) );
	}

Exit:
	if ( NT_ERROR(Status) )
	{
		DigiDump( DIGIWARN, ( "Dxb!ChildCreateDevice: Error creating PDO, status 0x%x\n", Status ) );
		if ( pChild )
		{
			if ( pChild->HardwareId.Buffer )
				DigiStrFreeUnicodeString( &pChild->HardwareId );

			if ( pChild->RegPath.Buffer )
				DigiStrFreeUnicodeString( &pChild->RegPath );

			if ( pChild->InstanceId.Buffer )
				DigiStrFreeUnicodeString( &pChild->InstanceId );
		}
	}
	return Status;
}


STATIC NTSTATUS
dxbChildEnumerateServiceDevices(
	PDXB_DEVICE_EXTENSION pExt,
	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:

	pExt
		Adapter 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 = dxbChildCreateDevice( pExt, 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 = dxbChildCreateDevice( pExt, 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( DIGIWARN, ( "Dxb!ChildEnumerateServiceDevices: Error opening Service key, %S\n", ServiceKey.Buffer ) );
		}
	}

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

	return Status;
}


NTSTATUS
DxbChildCreateChildList(
	PDXB_DEVICE_EXTENSION pExt,
	HANDLE                hkParameters )
/*++

Description:

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

Parameters:

	pExt
		Adapter device extension

	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( DIGIPNPCHILD, ( "Dxb!ChildCreateChildList: \n" ) );

	// 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( pExt->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 = dxbBuildOpenLineKey( &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 = dxbChildEnumerateServiceDevices( pExt, hkParameters, &ParamsKey, &ServiceName, iLine, iService );
			}

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

	return STATUS_SUCCESS;
}


VOID
DxbChildDeleteChildList(
	PDXB_DEVICE_EXTENSION pExt )
/*++

Description:

	Delete all child device objects on this adapter.

Parameters:

	pExt
		Adapter device extension

Return Value:

	VOID

--*/
{
	PLIST_ENTRY pEntry;
	PLIST_ENTRY pHead = &pExt->ChildListHead;
	DxbAdapterLock( pExt );
	pEntry = RemoveHeadList( &pExt->ChildListHead );
	DxbAdapterUnlock( pExt );

	// Must not hold spinlock while deleting device objects!
	while ( pEntry != pHead )
	{
		PDXB_CHILD_DEVICE pChild = CONTAINING_RECORD( pEntry, DXB_CHILD_DEVICE, ListEntry );
		ASSERT( pChild->Type == DXB_TYPE_CHILD_DEVICE );

        dxbChildDeleteDevice( pChild );

		// Get next entry
		DxbAdapterLock( pExt );
		pEntry = RemoveHeadList( &pExt->ChildListHead );
		DxbAdapterUnlock( pExt );
	}
}


NTSTATUS
DxbChildStartDevice(
	PDXB_CHILD_DEVICE  pExt,
	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:

	pExt
		Device extension for this child PDO.

	pIrp
		Request.

	pIos
		Parameters passed along with Irp.

Return Value:

	STATUS_SUCCESS, or appropriate error status.

--*/
{
	NTSTATUS Status = STATUS_SUCCESS;
	DigiDump( DIGIPNPCHILD, ( "Dxb!ChildStartDevice: pExt 0x%p\n", pExt ) );

	{
		HANDLE hkDriver;
		WCHAR buf[64] = {0};
		ULONG cb;
		UNICODE_STRING AdapterInstance = {0};	// Instance is last 4 digits of driver keyname + NULL
#define INSTANCE_DIGITS 5

		Status = IoGetDeviceProperty( pExt->pPhysicalDeviceObject, DevicePropertyDriverKeyName, sizeof buf, (PVOID)buf, &cb );
		if ( NT_SUCCESS( Status ) )
		{
			if ( cb > INSTANCE_DIGITS * sizeof (WCHAR) )
			{
				RtlInitUnicodeString( &AdapterInstance, &buf[cb/sizeof(WCHAR) - INSTANCE_DIGITS] );
			}
			else
				Status = STATUS_UNSUCCESSFUL;
		}

		if ( NT_SUCCESS( Status ) )
			Status = IoOpenDeviceRegistryKey( pExt->pPhysicalDeviceObject, PLUGPLAY_REGKEY_DRIVER, MAXIMUM_ALLOWED, &hkDriver );

		if ( NT_SUCCESS( Status ) )
		{
			HANDLE hkParameters;
			Status = DigiRegOpenSubkey( &hkParameters, hkDriver, DXB_REGKEY_PARAMETERS );
			if ( NT_SUCCESS( Status ) )
			{
				// Write the RegPath (== registry path) defining this object.  Provides a
				// link back to the subkey under the dxb adapter instance.  Used by coinstaller
				// and client drivers to get/set config params, since all params are stored
				// in the Dxb key, *not* in the client driver instance keys.
				Status = DigiRegWriteSz( hkParameters, DXB_REGVAL_REGPATH, pExt->RegPath.Buffer );

				// Write the Adapter Instance ("VAdapterNum") value.  Used by Isdn driver
				// to build GetAddressCaps strings.
				if ( NT_SUCCESS( Status ) )
					Status = DigiRegWriteSz( hkParameters, DXB_REGVAL_NDIS_ADAPTER_INSTANCE, AdapterInstance.Buffer );

				(void)DigiRegCloseKey( hkParameters );
			}
			ZwClose( hkDriver );
		}

		if ( NT_ERROR( Status ) )
		{
			DigiDump( DIGIWARN, ( "Dxb!ChildStartDevice: Error writing registry values, status 0x%x!\n", Status ) );
		}
	}

	return Status;
}


NTSTATUS
DxbChildRemoveDevice(
	PDXB_CHILD_DEVICE  pExt,
	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.

Return Value:

	This routine must return STATUS_SUCCESS.

--*/
{
	DigiDump( DIGIPNPCHILD, ( "Dxb!ChildRemoveDevice: pExt 0x%p\n", pExt ) );

    if (pExt->Removed)
    {
        // We initiated the REMOVE - we need to free the Port PDO memory and free the device object.
        dxbChildDeleteDevice(pExt);
    }

    return STATUS_SUCCESS;
}


NTSTATUS
DxbChildDispatch(
	PDXB_CHILD_DEVICE  pExt,
	PIRP               pIrp,
	PIO_STACK_LOCATION pIos )
/*++

Description:

	Dispatch routine for all child PDO related PNP requests.

Parameters:

	pExt
		Device extension for the PDO.

	pIrp
		Request.

	pIos
		Parameters passed along with Irp.

Return Value:

	STATUS_SUCCESS, or some error value.

--*/
{
	ULONG const Minor = pIos->MinorFunction;

	// Some PNP Irps require that we complete the Irp w/o modifying the status value!!
	NTSTATUS Status = pIrp->IoStatus.Status;
	ULONG_PTR Information = pIrp->IoStatus.Information;
	ASSERT( pExt->Type = DXB_TYPE_CHILD_DEVICE );

	DigiDump( DIGIPNPLOUD, ( "Dxb!ChildDispatch: %s, pExt 0x%p, pIrp 0x%p\n", DxbGetPnpIrpName(Minor), pExt, pIrp ) );

	switch ( Minor )
	{
		case IRP_MN_QUERY_STOP_DEVICE:
		case IRP_MN_STOP_DEVICE:
		case IRP_MN_CANCEL_STOP_DEVICE:
		case IRP_MN_QUERY_REMOVE_DEVICE:
		case IRP_MN_CANCEL_REMOVE_DEVICE:
		case IRP_MN_SURPRISE_REMOVAL:
			Status = STATUS_SUCCESS;
			Information = 0;
			break;

		case IRP_MN_START_DEVICE:
			Status = DxbChildStartDevice( pExt, pIrp, pIos );
			Information = 0;
			break;

		case IRP_MN_REMOVE_DEVICE:
			Status = DxbChildRemoveDevice( pExt, pIrp, pIos );
			Information = 0;
			break;

		case IRP_MN_QUERY_PNP_DEVICE_STATE:
			Status = STATUS_SUCCESS;
			Information |= pExt->DeviceStateFlags;
			break;

		case IRP_MN_QUERY_CAPABILITIES:
		{
			PDEVICE_CAPABILITIES pCaps = pIos->Parameters.DeviceCapabilities.Capabilities;
			ASSERT( pCaps->Size == sizeof *pCaps );
			pCaps->LockSupported = FALSE;
			pCaps->EjectSupported = FALSE;
			pCaps->Removable = FALSE;
			pCaps->DockDevice = FALSE;
			pCaps->UniqueID = FALSE;
			pCaps->SilentInstall = TRUE;
			pCaps->RawDeviceOK = FALSE;
//			pCaps->Address = ;	// leave at default of 0xFFFFFFFF
//			pCaps->UINumber = ;	// leave at default of 0xFFFFFFFF
			pCaps->DeviceState[PowerSystemWorking]   = PowerDeviceD0;
			pCaps->DeviceState[PowerSystemSleeping1] = PowerDeviceD3;
			pCaps->DeviceState[PowerSystemSleeping2] = PowerDeviceD3;
			pCaps->DeviceState[PowerSystemSleeping3] = PowerDeviceD3;
			pCaps->DeviceState[PowerSystemHibernate] = PowerDeviceD3;
			pCaps->DeviceState[PowerSystemShutdown]  = PowerDeviceD3;
			pCaps->SystemWake = PowerSystemUnspecified;
			pCaps->DeviceWake = PowerSystemUnspecified;
			#define ONE_SECOND 10000
			pCaps->D1Latency = 0;
			pCaps->D2Latency = 0;
			pCaps->D3Latency = 20 * ONE_SECOND;

			Status = STATUS_SUCCESS;
			Information = 0;
			break;
		}

		case IRP_MN_QUERY_DEVICE_RELATIONS:
		{
			DigiDump( DIGIPNPCHILD, ( "Dxb!ChildQueryDeviceRelations: pExt 0x%p, QueryType %s\n", pExt,
			          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 TargetDeviceRelation:
				{
					// Must be ExAllocated from PagedPool!  Freed later by PNP manager
					PDEVICE_RELATIONS pRelations = ExAllocatePoolWithTag( PagedPool, sizeof *pRelations, DefaultPoolTag );
					if ( !pRelations )
					{
						Status = STATUS_INSUFFICIENT_RESOURCES;
						Information = 0;
						break;
					}
					pRelations->Count = 1;
					pRelations->Objects[0] = pExt->pPhysicalDeviceObject;

					// Reference it because we're returning a pointer to it.  PNP Manager will
					// decrement reference when it's done.
					ObReferenceObject( pExt->pPhysicalDeviceObject );
					Status = STATUS_SUCCESS;
					Information = (ULONG_PTR)pRelations;
					break;
				}

				case EjectionRelations:
				case PowerRelations:
				case RemovalRelations:
				case BusRelations:
				default:
				{
					// PDOs just complete enumeration requests without altering the status.
					Status = pIrp->IoStatus.Status;
					break;
				}
			}
			break;
		}

		case IRP_MN_QUERY_ID:
		{
			UNICODE_STRING BusName;
			RtlInitUnicodeString( &BusName, TEXT("Dxb\\") );

			switch ( pIos->Parameters.QueryId.IdType )
			{
				case BusQueryDeviceID:
				{
					// DeviceId is <Bus>\<HardwareId>, i.e. "DXB\DIGIPRI"
					// DeviceId is not multi_sz so terminate with single NULL.
					USHORT Length = BusName.Length + pExt->HardwareId.Length + sizeof(WCHAR);
					PWCHAR pBuf = ExAllocatePoolWithTag( NonPagedPool, Length, DefaultPoolTag );
					if ( !pBuf )
					{
						Status = STATUS_INSUFFICIENT_RESOURCES;
						Information = 0;
					}
					else
					{
						UNICODE_STRING Id;
						Id.Buffer = pBuf;
						Id.Length = 0;
						Id.MaximumLength = Length;
						RtlCopyUnicodeString( &Id, &BusName );
						RtlAppendUnicodeStringToString( &Id, &pExt->HardwareId );
						DigiDump( DIGIPNPCHILD, ( "Dxb!ChildDispatch: BusQueryDeviceId: %S\n", Id.Buffer ) );

						Status = STATUS_SUCCESS;
						Information = (ULONG_PTR)Id.Buffer;
					}
					break;
				}

				case BusQueryInstanceID:
				{
					// InstanceId is <iService><iLine><iChild>, i.e. "010001"
					// InstanceId is not multi_sz so terminate with single NULL.
					USHORT Length = pExt->InstanceId.Length + sizeof(WCHAR);
					PWSTR buf = ExAllocatePoolWithTag( NonPagedPool, Length, DefaultPoolTag );
					if ( !buf )
					{
						Status = STATUS_INSUFFICIENT_RESOURCES;
						Information = 0;
					}
					else
					{
						UNICODE_STRING Id;
						Id.Buffer = buf;
						Id.Length = 0;
						Id.MaximumLength = Length;
						RtlCopyUnicodeString( &Id, &pExt->InstanceId );
						DigiDump( DIGIPNPCHILD, ( "Dxb!ChildDispatch: BusQueryInstanceId: %S\n", Id.Buffer ) );
						Status = STATUS_SUCCESS;
						Information = (ULONG_PTR)Id.Buffer;
					}
					break;
				}

				case BusQueryHardwareIDs:
				{
					// HardwareId is <Bus>\<HardwareId>, i.e. "DXB\DIGIPRI"
					// ** HardwareIDs is multi_sz so terminate with double NULL **
					USHORT Length = BusName.Length + pExt->HardwareId.Length + 2*sizeof(WCHAR);
					PWCHAR pBuf = ExAllocatePoolWithTag( NonPagedPool, Length, DefaultPoolTag );
					if ( !pBuf )
					{
						Status = STATUS_INSUFFICIENT_RESOURCES;
						Information = 0;
					}
					else
					{
						UNICODE_STRING Id;
						Id.Buffer = pBuf;
						Id.Length = 0;
						Id.MaximumLength = Length;
						RtlZeroMemory( pBuf, Length );	// ensure double-null termination
						RtlCopyUnicodeString( &Id, &BusName );
						RtlAppendUnicodeStringToString( &Id, &pExt->HardwareId );
						DigiDump( DIGIPNPCHILD, ( "Dxb!ChildDispatch: BusQueryHardwareIDs: %S\n", Id.Buffer ) );
						Status = STATUS_SUCCESS;
						Information = (ULONG_PTR)Id.Buffer;
					}
					break;
				}

				case BusQueryCompatibleIDs:
				{
					// ** CompatibleIDs is multi_sz so terminate with double NULL **
					DigiDump( DIGIPNPCHILD, ( "Dxb!ChildDispatch: BusQueryCompatibleIds, none.\n") );
					Status = STATUS_SUCCESS;
					Information = 0;
					break;
				}

				default:
				{
					DigiDump( DIGIWARN, ( "Dxb!ChildDispatch: *** Unexpected QUERY_ID type, 0x%x! ***\n", pIos->Parameters.QueryId.IdType ) );
					Status = STATUS_NOT_SUPPORTED;
					Information = 0;
					break;
				}
			}
			break;
		}

		case IRP_MN_QUERY_DEVICE_TEXT:
		{
			switch ( pIos->Parameters.QueryDeviceText.DeviceTextType )
			{
				case DeviceTextDescription:
				{
					WCHAR Description[] = L"Digi PnP Device";
					PWCHAR pDescription = ExAllocatePoolWithTag( NonPagedPool, (wcslen(Description)+ 1)*sizeof(WCHAR), DefaultPoolTag );
					if ( !pDescription )
					{
						Status = STATUS_INSUFFICIENT_RESOURCES;
						Information = 0;
						break;
					}
					wcscpy( pDescription, Description );
					DigiDump( DIGIPNPCHILD, ( "Dxb!ChildDispatch: DeviceTextDescription: %S\n", pDescription ) );
					Status = STATUS_SUCCESS;
					Information = (ULONG_PTR)pDescription;
					break;
				}

				case DeviceTextLocationInformation:
				{
				/*
					WCHAR Location[] = L"Digi PnP Adapter";
					PWCHAR pLocation = ExAllocatePoolWithTag( NonPagedPool, (wcslen(Location)+ 1)*sizeof(WCHAR), DefaultPoolTag );
					if ( !pLocation )
					{
						Status = STATUS_INSUFFICIENT_RESOURCES;
						Information = 0;
						break;
					}
					wcscpy( pLocation, Location );
					DigiDump( DIGIPNPCHILD, ( "Dxb!ChildDispatch: DeviceTextLocationInformation, %S\n", pLocation ) );
					Status = STATUS_SUCCESS;
					Information = (ULONG_PTR)pLocation;
					break;
				*/
					// If we do not satisfy this request, the PnP default handler
                    // will put in all the information we dream of ;-)
                    Status = STATUS_NOT_SUPPORTED;
					Information = 0;
                    break;
				}

				default:
				{
					DigiDump( DIGIWARN, ( "Dxb!ChildDispatch: *** Unexpected QUERY_DEVICE_TEXT type, 0x%x! ***\n", pIos->Parameters.QueryDeviceText.DeviceTextType ) );
					Status = STATUS_NOT_SUPPORTED;
					Information = 0;
					break;
				}
			}
			break;
		}

//		case IRP_MN_QUERY_BUS_INFORMATION:			break;
//		case IRP_MN_FILTER_RESOURCE_REQUIREMENTS:	break;
//		case IRP_MN_DEVICE_USAGE_NOTIFICATION:		break;
//		case IRP_MN_QUERY_RESOURCES:					break;
//		case IRP_MN_QUERY_RESOURCE_REQUIREMENTS:	break;

//		case IRP_MN_QUERY_INTERFACE:					break;
//		case IRP_MN_READ_CONFIG:						break;
//		case IRP_MN_WRITE_CONFIG:						break;
//		case IRP_MN_EJECT:								break;
//		case IRP_MN_SET_LOCK:							break;
//		case IRP_MN_QUERY_LEGACY_BUS_INFORMATION:	break;

		default:
			DigiDump( DIGIPNPLOUD, ( "Dxb!ChildDispatch: PNP Irp not supported, %s, (0x%x)!\n", DxbGetPnpIrpName(Minor), Minor ) );
            Status = pIrp->IoStatus.Status;
            IoCompleteRequest( pIrp, IO_NO_INCREMENT );
            return Status;
			break;
	}
	dxbIoCompleteRequest( pIrp, Status, Information );

	if ( NT_ERROR( Status ) )
	{
		DigiDump( DIGIPNPLOUD, ( "Dxb!ChildDispatch: %s, returning failure status 0x%x\n", DxbGetPnpIrpName(Minor), Status ) );
	}
	return Status;
}

#endif	// NT5_BUILD
