/*++

Copyright (c) 1991  Microsoft Corporation
All rights reserved

Module Name:

    pnpdd.c

Abstract:

    This module implements new Plug-And-Play driver entries and IRPs.

Author:

    Shie-Lin Tzong (shielint) June-16-1995

Environment:

    Kernel mode only.

Revision History:

*/

#include "iop.h"

#if _PNP_POWER_

typedef struct _DEVICE_CHANGE_COMPLETION_CONTEXT {
    KEVENT Event;
    IO_STATUS_BLOCK IoStatus;
} DEVICE_CHANGE_COMPLETION_CONTEXT, *PDEVICE_CHANGE_COMPLETION_CONTEXT;

NTSTATUS
IopReconfigureResources(
    IN PDRIVER_OBJECT DriverObject,
    IN PDEVICE_OBJECT DeviceObject OPTIONAL,
    IN DRIVER_RECONFIGURE_OPERATION Operation,
    IN PCM_RESOURCE_LIST CmResources
    );

NTSTATUS
IoAddDevice (
   IN PDRIVER_OBJECT DriverObject,
   IN PUNICODE_STRING ServiceKeyName,
   IN OUT PULONG InstanceNumber
   );

NTSTATUS
IoRemoveDevice (
    IN PDEVICE_OBJECT TargetDevice,
    IN ULONG IrpMinorCode
    );

NTSTATUS
IopDeviceRemovalComplete (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PVOID Context
    );

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, IopReconfigureResources)
#pragma alloc_text(PAGE, IoAddDevice)
#pragma alloc_text(PAGE, IoRemoveDevice)
#endif

NTSTATUS
IopReconfigureResources(
    IN PDRIVER_OBJECT DriverObject,
    IN PDEVICE_OBJECT DeviceObject OPTIONAL,
    IN DRIVER_RECONFIGURE_OPERATION Operation,
    IN PCM_RESOURCE_LIST CmResources
    )
/*++

Routine Description:

    This function sends Reconfiguration related requests to device drivers.

Arguments:

    DriverObject - Supplies the driver object of the device driver being queried
        or reconfigured.

    DeviceObject - Supplies the device object of the device being queried or
        reconfigured.  If the resources being reconfigured are driver specific,
        this parameter will be NULL.

    Operation - Specifies the operation requested.

    ResourceList - Supplies the new resource list for the device/driver.
        This parameter is specified only if Operation is either
        QueryReconfigureResources or ReconfigureResources.

Return Value:

    NTSTATUS code.

--*/
{
    PDRIVER_RECONFIGURE_DEVICE reconfigRoutine;
    NTSTATUS status = STATUS_NOT_IMPLEMENTED;

    PAGED_CODE();

    reconfigRoutine = DriverObject->DriverExtension->ReconfigureDevice;
    if (reconfigRoutine != NULL) {
        status = (reconfigRoutine) ( DriverObject,
                                     DeviceObject,
                                     Operation,
                                     CmResources
                                     );
    }
    return status;
}

NTSTATUS
IoAddDevice (
   IN PDRIVER_OBJECT DriverObject,
   IN PUNICODE_STRING ServiceKeyName,
   IN OUT PULONG InstanceNumber
   )

/*++

Routine Description:

    This functions is used by Pnp manager to inform device driver to add a device to
    its device list or is used by Pnp enumerator to give device driver  a chance to
    report/detect devices at run time.

    If run time device detection is desired, the AddDevice entry will be invoked with
    InstanceNumber = PLUG_PLAY_NO_INSTANCE.  (For example, bus extender drivers will
    use this interface to report new bus detected at run time.)  If the driver
    responses the call with a new device instance, it will be called repeatedly until
    a STATUS_NO_MORE_ENTRIES is returned. For a driver which does not support run time
    detection, it can simply return STATUS_NO_MORE_ENTRIES when its AddDevice entry is
    invoked with InstanceNumber =  PLUG_PLAY_NO_INSTANCE.

Parameters:

    DriverObject - Supplies a driver object to receive the add device request.

    ServiceKeyName - Supplies the name of the subkey in the system service list
        (HKEY_LOCAL_MACHINE\CurrentControlSet\Services) that caused the driver to load.

    InstanceNumber - Supplies an ordinal value indicating the device instance to be
        added or PLUG_PLAY_NO_INSTANCE to initiate device/bus run time detection.

Return Value:

    NTSTATUS code.

--*/
{
    PDRIVER_ADD_DEVICE addDeviceRoutine;
    NTSTATUS status = STATUS_NOT_IMPLEMENTED;

    PAGED_CODE();

    addDeviceRoutine = DriverObject->DriverExtension->AddDevice;
    if (addDeviceRoutine != NULL) {
        status = (addDeviceRoutine) ( ServiceKeyName, InstanceNumber );
    }
    return status;
}

NTSTATUS
IoRemoveDevice (
    IN PDEVICE_OBJECT TargetDevice,
    IN ULONG IrpMinorCode
    )

/*++

Routine Description:

    This function sends a requested DeviceRemoval irp to the top level device
    object which roots on TargetDevice.  If there is a VPB associated with the
    TargetDevice, the corresponding filesystem's VDO will be used.  Otherwise
    the irp will be sent directly to the target device/ or its assocated device
    object.

Parameters:

    TargetDevice - Supplies the device object of the device being removed.

    Operation - Specifies the operation requested.
        The following IRP codes are used with IRP_MJ_DEVICE_CHANGE for removing
        devices:
            IRP_MN_QUERY_REMOVE_DEVICE
            IRP_MN_CANCEL_REMOVE_DEVICE
            IRP_MN_REMOVE_DEVICE

Return Value:

    NTSTATUS code.

--*/
{
    PDEVICE_OBJECT deviceObject;
    PDEVOBJ_EXTENSION deviceExtension;
    PIRP irp;
    PIO_STACK_LOCATION irpSp;
    DEVICE_CHANGE_COMPLETION_CONTEXT completionContext;
    NTSTATUS status;

    PAGED_CODE();

    ASSERT(IrpMinorCode == IRP_MN_QUERY_REMOVE_DEVICE ||
           IrpMinorCode == IRP_MN_CANCEL_REMOVE_DEVICE ||
           IrpMinorCode == IRP_MN_REMOVE_DEVICE);

    //
    // If the target device object has a VPB associated with it.
    // make the filesystem's volume device object the irp target.
    //

    if (TargetDevice->Vpb) {
        deviceObject = TargetDevice->Vpb->DeviceObject;
    } else {
        deviceObject = TargetDevice;
    }

    //
    // Get a pointer to the topmost device object in the stack of devices,
    // beginning with the deviceObject.
    //

    deviceObject = IoGetAttachedDevice(deviceObject);
    deviceExtension = deviceObject->DeviceObjectExtension;

    //
    // Allocate an I/O Request Packet (IRP) for this device removal operation.
    //

    irp = IoAllocateIrp( (CCHAR) (deviceObject->StackSize + 1), FALSE );
    if (!irp) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // Copy some state into the first stack location
    //

    irpSp = IoGetNextIrpStackLocation (irp);
    irpSp->Parameters.Others.Argument1 = (PVOID) TargetDevice;
    irpSp->Parameters.Others.Argument2 = (PVOID) IrpMinorCode;

    //
    // Get a pointer to the next stack location in the packet.  This location
    // will be used to pass the function codes and parameters to the first
    // driver.
    //

    IoSetNextIrpStackLocation (irp);
    irpSp = IoGetNextIrpStackLocation (irp);

    //
    // Fill in the IRP according to this request.
    //

    irp->Tail.Overlay.Thread = PsGetCurrentThread();
    irp->RequestorMode = KernelMode;
    irp->UserIosb = NULL;
    irp->UserEvent = NULL;
    irpSp->MajorFunction = IRP_MJ_DEVICE_CHANGE;
    irpSp->MinorFunction = (UCHAR)IrpMinorCode;
    irpSp->Parameters.RemoveDevice.DeviceToRemove = TargetDevice;

    KeInitializeEvent( &completionContext.Event, SynchronizationEvent, FALSE );
    IoSetCompletionRoutine(irp,
                           IopDeviceRemovalComplete,
                           &completionContext, /* Completion context */
                           TRUE,               /* Invoke on success  */
                           TRUE,               /* Invoke on error    */
                           TRUE                /* Invoke on cancel   */
                           );

    //
    // Hold the device object's Device Queue and Send the request packet to the
    // appropriate driver...
    //

    IoHoldDeviceQueue(deviceObject, irp);
    status = IoCallDriver( deviceObject, irp );
    if (status == STATUS_PENDING) {
        (VOID) KeWaitForSingleObject( &completionContext.Event,
                                      Executive,
                                      KernelMode,
                                      FALSE,
                                      (PLARGE_INTEGER) NULL );
    }
    return completionContext.IoStatus.Status;
}

NTSTATUS
IopDeviceRemovalComplete (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PVOID Context
    )
/*++

Routine Description:

    Completion function for a DeviceRemoval IRP. The device objects'
    StartIoHoldingQueue will be released if no other queue holding irp
    pending.

Arguments:

    DeviceObject - NULL.
    Irp          - SetPower irp which has completed
    Context      - a pointer to the DEVICE_CHANGE_COMPLETION_CONTEXT.

Return Value:

    STATUS_MORE_PROCESSING_REQUIRED is returned to IoCompleteRequest
    to signify that IoCompleteRequest should not continue processing
    the IRP.

--*/
{
    PIO_STACK_LOCATION irpSp;
    PDEVOBJ_EXTENSION deviceObjectExt;
    PDEVICE_OBJECT deviceObject;
    ULONG irpCode;

    //
    // Read state from Irp.
    //

    irpSp = IoGetCurrentIrpStackLocation (Irp);
    deviceObject = irpSp->DeviceObject;
    irpCode = (ULONG)irpSp->Parameters.Others.Argument2;

    if ((irpCode == IRP_MN_QUERY_REMOVE_DEVICE && !NT_SUCCESS (Irp->IoStatus.Status)) ||
        irpCode != IRP_MN_REMOVE_DEVICE) {

        //
        // Release any irps which were postponded from IoStartPacket if
        // it is NOT a device removal irp and if a driver returned failure to query.
        //

        IoReleaseStartIoHoldingQueue(deviceObject);
    }

    //
    // Irp processing is complete, free the irp and then return
    // more_processing_requered which causes IoCompleteRequest to
    // stop "completing" this irp any future.
    //

    IoFreeIrp (Irp);
    return STATUS_MORE_PROCESSING_REQUIRED;
}
#endif // _PNP_POWER_