623 lines
20 KiB
C
623 lines
20 KiB
C
/*++
|
|
|
|
Copyright (c) 2000 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
pnp.c
|
|
|
|
Abstract: This module contains code to handle PnP and Power IRPs.
|
|
|
|
Environment:
|
|
|
|
Kernel mode
|
|
|
|
Author:
|
|
|
|
Michael Tsang (MikeTs) 13-Mar-2000
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "pch.h"
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGE, HpenPnp)
|
|
#pragma alloc_text(PAGE, HpenPower)
|
|
#pragma alloc_text(PAGE, InitDevice)
|
|
#pragma alloc_text(PAGE, RemoveDevice)
|
|
#pragma alloc_text(PAGE, SendSyncIrp)
|
|
#endif
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc EXTERNAL
|
|
*
|
|
* @func NTSTATUS | HpenPnp |
|
|
* Plug and Play dispatch routine for this driver.
|
|
*
|
|
* @parm IN PDEVICE_OBJECT | DevObj | Pointer to the device object.
|
|
* @parm IN PIRP | Irp | Pointer to an I/O request packet.
|
|
*
|
|
* @rvalue SUCCESS | returns STATUS_SUCCESS
|
|
* @rvalue FAILURE | returns NT status code
|
|
*
|
|
*****************************************************************************/
|
|
|
|
NTSTATUS EXTERNAL
|
|
HpenPnp(
|
|
IN PDEVICE_OBJECT DevObj,
|
|
IN PIRP Irp
|
|
)
|
|
{
|
|
PROCNAME("HpenPnp")
|
|
NTSTATUS status;
|
|
PIO_STACK_LOCATION irpsp;
|
|
PDEVICE_EXTENSION devext;
|
|
|
|
PAGED_CODE();
|
|
|
|
irpsp = IoGetCurrentIrpStackLocation(Irp);
|
|
|
|
ENTER(1, ("(DevObj=%p,Irp=%p,IrpSp=%p,Minor=%s)\n",
|
|
DevObj, Irp, irpsp,
|
|
LookupName(irpsp->MinorFunction, PnPMinorFnNames)));
|
|
|
|
devext = GET_MINIDRIVER_DEVICE_EXTENSION(DevObj);
|
|
status = IoAcquireRemoveLock(&devext->RemoveLock, Irp);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
//
|
|
// Someone sent us another plug and play IRP after removed
|
|
//
|
|
ERRPRINT(("received PnP IRP after device was removed\n"));
|
|
|
|
Irp->IoStatus.Information = 0;
|
|
Irp->IoStatus.Status = status;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
}
|
|
else
|
|
{
|
|
BOOLEAN fSkipIt = FALSE;
|
|
|
|
switch (irpsp->MinorFunction)
|
|
{
|
|
case IRP_MN_START_DEVICE:
|
|
ASSERT(!(devext->dwfHPen & HPENF_DEVICE_STARTED));
|
|
//
|
|
// Forward the IRP down the stack
|
|
//
|
|
status = SendSyncIrp(devext->SerialDevObj, Irp, TRUE);
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
status = InitDevice(DevObj, Irp);
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
devext->dwfHPen |= HPENF_DEVICE_STARTED;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ERRPRINT(("failed to forward start IRP (status=%x)\n",
|
|
status));
|
|
}
|
|
|
|
Irp->IoStatus.Information = 0;
|
|
Irp->IoStatus.Status = status;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
break;
|
|
|
|
case IRP_MN_STOP_DEVICE:
|
|
//
|
|
// After the start IRP has been sent to the lower driver
|
|
// object, the bus may NOT send any more IRPS down ``touch''
|
|
// until another START has occured. Whatever access is
|
|
// required must be done before Irp passed on.
|
|
//
|
|
if (devext->dwfHPen & HPENF_DEVICE_STARTED)
|
|
{
|
|
devext->dwfHPen &= ~HPENF_DEVICE_STARTED;
|
|
//
|
|
// I don't need to cancel any pending read IRPs since
|
|
// those IRPs originated from hidclass and presumably
|
|
// hidclass will cancel them.
|
|
//
|
|
}
|
|
|
|
//
|
|
// We don't need a completion routine so fire and forget.
|
|
// Set the current stack location to the next stack location and
|
|
// call the next device object.
|
|
//
|
|
fSkipIt = TRUE;
|
|
Irp->IoStatus.Status = STATUS_SUCCESS;
|
|
break;
|
|
|
|
case IRP_MN_REMOVE_DEVICE:
|
|
case IRP_MN_SURPRISE_REMOVAL:
|
|
//
|
|
// The PlugPlay system has detected the removal of this device.
|
|
// We have no choice but to detach and delete the device object.
|
|
// (If we wanted to express an interest in preventing this
|
|
// removal, we should have filtered the query remove and query
|
|
// stop routines.)
|
|
// Note: we might receive a remove WITHOUT first receiving a
|
|
// stop.
|
|
//
|
|
|
|
//
|
|
// Make sure we do not allow more IRPs to start touching the
|
|
// device.
|
|
//
|
|
devext->dwfHPen &= ~HPENF_DEVICE_STARTED;
|
|
devext->dwfHPen |= HPENF_DEVICE_REMOVED;
|
|
|
|
RemoveDevice(DevObj, Irp);
|
|
|
|
//
|
|
// Send on the remove IRP
|
|
//
|
|
fSkipIt = TRUE;
|
|
Irp->IoStatus.Status = STATUS_SUCCESS;
|
|
break;
|
|
|
|
case IRP_MN_QUERY_CAPABILITIES:
|
|
status = SendSyncIrp(GET_NEXT_DEVICE_OBJECT(DevObj), Irp, TRUE);
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
PDEVICE_CAPABILITIES devcaps;
|
|
|
|
devcaps = irpsp->Parameters.DeviceCapabilities.Capabilities;
|
|
if (devcaps != NULL)
|
|
{
|
|
SYSTEM_POWER_STATE i;
|
|
|
|
//
|
|
// This device is built-in to the system, so it should
|
|
// be impossible to surprise remove this device, but
|
|
// we will handle it anyway.
|
|
//
|
|
devcaps->SurpriseRemovalOK = TRUE;
|
|
|
|
//
|
|
// While the underlying serial bus might be able to
|
|
// wake the machine from low power (via wake on ring),
|
|
// the tablet cannot.
|
|
//
|
|
devcaps->SystemWake = PowerSystemUnspecified;
|
|
devcaps->DeviceWake = PowerDeviceUnspecified;
|
|
devcaps->WakeFromD0 =
|
|
devcaps->WakeFromD1 =
|
|
devcaps->WakeFromD2 =
|
|
devcaps->WakeFromD3 = FALSE;
|
|
devcaps->DeviceState[PowerSystemWorking] =
|
|
PowerDeviceD0;
|
|
for (i = PowerSystemSleeping1;
|
|
i < PowerSystemMaximum;
|
|
i++)
|
|
{
|
|
devcaps->DeviceState[i] = PowerDeviceD3;
|
|
}
|
|
}
|
|
}
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
break;
|
|
|
|
default:
|
|
fSkipIt = TRUE;
|
|
break;
|
|
}
|
|
|
|
if (fSkipIt)
|
|
{
|
|
IoSkipCurrentIrpStackLocation(Irp);
|
|
ENTER(2, (".IoCallDriver(DevObj=%p,Irp=%p)\n",
|
|
GET_NEXT_DEVICE_OBJECT(DevObj), Irp));
|
|
status = IoCallDriver(GET_NEXT_DEVICE_OBJECT(DevObj), Irp);
|
|
EXIT(2, (".IoCallDriver=%x\n", status));
|
|
}
|
|
|
|
if (irpsp->MinorFunction == IRP_MN_REMOVE_DEVICE)
|
|
{
|
|
//
|
|
// Wait for the remove lock to free.
|
|
//
|
|
IoReleaseRemoveLockAndWait(&devext->RemoveLock, Irp);
|
|
|
|
IoFreeWorkItem(devext->ReadWorkItem[0].WorkItem);
|
|
IoFreeWorkItem(devext->ReadWorkItem[1].WorkItem);
|
|
}
|
|
else
|
|
{
|
|
IoReleaseRemoveLock(&devext->RemoveLock, Irp);
|
|
}
|
|
}
|
|
|
|
EXIT(1, ("=%x\n", status));
|
|
return status;
|
|
} //HpenPnp
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc EXTERNAL
|
|
*
|
|
* @func NTSTATUS | HpenPower | The power dispatch routine for this driver.
|
|
*
|
|
* @parm IN PDEVICE_OBJECT | DevObj | Points to the device object.
|
|
* @parm IN PIRP | Irp | Points to an I/O request packet.
|
|
*
|
|
* @rvalue SUCCESS | returns STATUS_SUCCESS
|
|
* @rvalue FAILURE | returns NT status code
|
|
*
|
|
*****************************************************************************/
|
|
|
|
NTSTATUS EXTERNAL
|
|
HpenPower(
|
|
IN PDEVICE_OBJECT DevObj,
|
|
IN PIRP Irp
|
|
)
|
|
{
|
|
PROCNAME("HpenPower")
|
|
NTSTATUS status;
|
|
PDEVICE_EXTENSION devext;
|
|
|
|
PAGED_CODE();
|
|
|
|
ENTER(1, ("(DevObj=%p,Irp=%p,Minor=%s)\n",
|
|
DevObj, Irp,
|
|
LookupName(IoGetCurrentIrpStackLocation(Irp)->MinorFunction,
|
|
PowerMinorFnNames)));
|
|
|
|
devext = GET_MINIDRIVER_DEVICE_EXTENSION(DevObj);
|
|
status = IoAcquireRemoveLock(&devext->RemoveLock, Irp);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
//
|
|
// Someone sent us another power IRP after removed
|
|
//
|
|
ERRPRINT(("received Power IRP after device was removed\n"));
|
|
Irp->IoStatus.Status = status;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
}
|
|
else
|
|
{
|
|
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(Irp);
|
|
POWER_STATE_TYPE PowerType = irpsp->Parameters.Power.Type;
|
|
POWER_STATE NewPowerState = irpsp->Parameters.Power.State;
|
|
BOOLEAN fSkipCalldown = FALSE;
|
|
|
|
switch (irpsp->MinorFunction)
|
|
{
|
|
case IRP_MN_SET_POWER:
|
|
//
|
|
// We only handle DevicePowerState IRPs that change
|
|
// power states.
|
|
//
|
|
if ((PowerType == DevicePowerState) &&
|
|
(NewPowerState.DeviceState != devext->PowerState))
|
|
{
|
|
DBGPRINT(1, ("power state change (%s->%s)\n",
|
|
LookupName(devext->PowerState,
|
|
PowerStateNames),
|
|
LookupName(NewPowerState.DeviceState,
|
|
PowerStateNames)));
|
|
switch (NewPowerState.DeviceState)
|
|
{
|
|
case PowerDeviceD0:
|
|
//
|
|
// Transitioning from a low D state to D0.
|
|
//
|
|
status = SendSyncIrp(GET_NEXT_DEVICE_OBJECT(DevObj),
|
|
Irp,
|
|
TRUE);
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
PoSetPowerState(DevObj,
|
|
PowerType,
|
|
NewPowerState);
|
|
OemWakeupDevice(devext);
|
|
devext->PowerState = NewPowerState.DeviceState;
|
|
devext->dwfHPen &= ~HPENF_TABLET_STANDBY;
|
|
}
|
|
Irp->IoStatus.Status = status;
|
|
IoReleaseRemoveLock(&devext->RemoveLock, Irp);
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
fSkipCalldown = TRUE;
|
|
break;
|
|
|
|
case PowerDeviceD1:
|
|
case PowerDeviceD2:
|
|
case PowerDeviceD3:
|
|
//BUGBUG: OemStandbyDevice(devext);
|
|
PoSetPowerState(DevObj, PowerType, NewPowerState);
|
|
devext->PowerState = NewPowerState.DeviceState;
|
|
//BUGBUG: devext->dwfHPen |= HPENF_TABLET_STANDBY;
|
|
IoReleaseRemoveLock(&devext->RemoveLock, Irp);
|
|
|
|
Irp->IoStatus.Status = STATUS_SUCCESS;
|
|
IoSkipCurrentIrpStackLocation(Irp);
|
|
|
|
ENTER(2, (".PoCallDriver(DevObj=%p,Irp=%p)\n",
|
|
GET_NEXT_DEVICE_OBJECT(DevObj), Irp));
|
|
status = PoCallDriver(
|
|
GET_NEXT_DEVICE_OBJECT(DevObj),
|
|
Irp);
|
|
EXIT(2, (".PoCallDriver=%x\n", status));
|
|
|
|
fSkipCalldown = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case IRP_MN_WAIT_WAKE:
|
|
case IRP_MN_QUERY_POWER:
|
|
break;
|
|
|
|
default:
|
|
ERRPRINT(("unsupported power IRP (%s)\n",
|
|
LookupName(irpsp->MinorFunction, PowerMinorFnNames)));
|
|
break;
|
|
}
|
|
|
|
if (!fSkipCalldown)
|
|
{
|
|
IoSkipCurrentIrpStackLocation(Irp);
|
|
ENTER(2, (".PoCallDriver(DevObj=%p,Irp=%p)\n",
|
|
GET_NEXT_DEVICE_OBJECT(DevObj), Irp));
|
|
status = PoCallDriver(GET_NEXT_DEVICE_OBJECT(DevObj), Irp);
|
|
EXIT(2, (".PoCallDriver=%x\n", status));
|
|
|
|
IoReleaseRemoveLock(&devext->RemoveLock, Irp);
|
|
}
|
|
}
|
|
|
|
EXIT(1, ("=%x\n", status));
|
|
return status;
|
|
} //HpenPower
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func NTSTATUS | InitDevice |
|
|
* Get the device information and attempt to initialize a
|
|
* configuration for a device. If we cannot identify this as a
|
|
* valid HID device or configure the device, our start device
|
|
* function is failed.
|
|
*
|
|
* @parm IN PDEVICE_OBJECT | DevObj | Points to the device object.
|
|
* @parm IN PIRP | Irp | Points to an I/O request packet.
|
|
*
|
|
* @rvalue SUCCESS | returns STATUS_SUCCESS
|
|
* @rvalue FAILURE | returns NT status code
|
|
*
|
|
*****************************************************************************/
|
|
|
|
NTSTATUS INTERNAL
|
|
InitDevice(
|
|
IN PDEVICE_OBJECT DevObj,
|
|
IN PIRP Irp
|
|
)
|
|
{
|
|
PROCNAME("InitDevice")
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
PDEVICE_EXTENSION devext;
|
|
|
|
PAGED_CODE();
|
|
|
|
ENTER(2, ("(DevObj=%p,Irp=%p)\n", DevObj, Irp));
|
|
|
|
devext = GET_MINIDRIVER_DEVICE_EXTENSION(DevObj);
|
|
|
|
if (!(devext->dwfHPen & HPENF_SERIAL_OPENED))
|
|
{
|
|
//
|
|
// If a create hasn't been sent down the stack, send one now.
|
|
// The serial port driver requires a create before it will accept
|
|
// any reads or ioctls.
|
|
//
|
|
PIO_STACK_LOCATION irpspNext = IoGetNextIrpStackLocation(Irp);
|
|
NTSTATUS PrevStatus = Irp->IoStatus.Status;
|
|
ULONG_PTR PrevInfo = Irp->IoStatus.Information;
|
|
|
|
RtlZeroMemory(irpspNext, sizeof(*irpspNext));
|
|
irpspNext->MajorFunction = IRP_MJ_CREATE;
|
|
status = SendSyncIrp(GET_NEXT_DEVICE_OBJECT(DevObj), Irp, FALSE);
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
devext->dwfHPen |= HPENF_SERIAL_OPENED;
|
|
Irp->IoStatus.Status = PrevStatus;
|
|
Irp->IoStatus.Information = PrevInfo;
|
|
}
|
|
else
|
|
{
|
|
ERRPRINT(("failed to send CREATE IRP to serial port (status=%x)\n",
|
|
status));
|
|
}
|
|
}
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
status = OemInitSerialPort(devext);
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
status = OemInitDevice(devext);
|
|
}
|
|
}
|
|
|
|
EXIT(2, ("=%x\n", status));
|
|
return status;
|
|
} //InitDevice
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func VOID | RemoveDevice | FDO Remove routine
|
|
*
|
|
* @parm IN PDEVICE_OBJECT | DevObj | Points to the device object.
|
|
* @parm IN PIRP | Irp | Points to an I/O request packet.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
VOID INTERNAL
|
|
RemoveDevice(
|
|
PDEVICE_OBJECT DevObj,
|
|
PIRP Irp
|
|
)
|
|
{
|
|
PROCNAME("RemoveDevice")
|
|
PDEVICE_EXTENSION devext;
|
|
|
|
PAGED_CODE();
|
|
|
|
ENTER(2, ("(DevObj=%p,Irp=%p)\n", DevObj, Irp));
|
|
|
|
devext = GET_MINIDRIVER_DEVICE_EXTENSION(DevObj);
|
|
|
|
ASSERT(devext->dwfHPen & HPENF_DEVICE_REMOVED);
|
|
if (devext->dwfHPen & HPENF_SERIAL_OPENED)
|
|
{
|
|
PIO_STACK_LOCATION irpspNext;
|
|
|
|
OemRemoveDevice(devext);
|
|
|
|
irpspNext = IoGetNextIrpStackLocation(Irp);
|
|
RtlZeroMemory(irpspNext, sizeof(*irpspNext));
|
|
irpspNext->MajorFunction = IRP_MJ_CLEANUP;
|
|
SendSyncIrp(GET_NEXT_DEVICE_OBJECT(DevObj), Irp, FALSE);
|
|
|
|
irpspNext = IoGetNextIrpStackLocation(Irp);
|
|
RtlZeroMemory(irpspNext, sizeof(*irpspNext));
|
|
irpspNext->MajorFunction = IRP_MJ_CLOSE;
|
|
SendSyncIrp(GET_NEXT_DEVICE_OBJECT(DevObj), Irp, FALSE);
|
|
|
|
devext->dwfHPen &= ~HPENF_SERIAL_OPENED;
|
|
}
|
|
#ifdef DEBUG
|
|
ExAcquireFastMutex(&gmutexDevExtList);
|
|
RemoveEntryList(&devext->List);
|
|
ExReleaseFastMutex (&gmutexDevExtList);
|
|
#endif
|
|
|
|
EXIT(2, ("!\n"));
|
|
return;
|
|
} //RemoveDevice
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func NTSTATUS | SendSyncIrp |
|
|
* Send an IRP synchronously down the stack.
|
|
*
|
|
* @parm IN PDEVICE_OBJECT | DevObj | Points to the device object.
|
|
* @parm IN PIRP | Irp | Points to the IRP.
|
|
* @parm IN BOOLEAN | fCopyToNext | if TRUE, copy the irpsp to next location.
|
|
*
|
|
* @rvalue SUCCESS | returns STATUS_SUCCESS
|
|
* @rvalue FAILURE | returns NT status code
|
|
*
|
|
*****************************************************************************/
|
|
|
|
NTSTATUS INTERNAL
|
|
SendSyncIrp(
|
|
IN PDEVICE_OBJECT DevObj,
|
|
IN PIRP Irp,
|
|
IN BOOLEAN fCopyToNext
|
|
)
|
|
{
|
|
PROCNAME("SendSyncIrp")
|
|
NTSTATUS status;
|
|
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(Irp);
|
|
KEVENT event;
|
|
|
|
PAGED_CODE();
|
|
|
|
ENTER(2, ("(DevObj=%p,Irp=%p,fCopyToNext=%x,MajorFunc=%s)\n",
|
|
DevObj, Irp, fCopyToNext,
|
|
LookupName(irpsp->MajorFunction, MajorFnNames)));
|
|
|
|
KeInitializeEvent(&event, SynchronizationEvent, FALSE);
|
|
if (fCopyToNext)
|
|
{
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
}
|
|
|
|
IoSetCompletionRoutine(Irp, IrpCompletion, &event, TRUE, TRUE, TRUE);
|
|
if (irpsp->MajorFunction == IRP_MJ_POWER)
|
|
{
|
|
ENTER(2, (".PoCallDriver(DevObj=%p,Irp=%p)\n", DevObj, Irp));
|
|
status = PoCallDriver(DevObj, Irp);
|
|
EXIT(2, (".IoCallDriver=%x\n", status));
|
|
}
|
|
else
|
|
{
|
|
ENTER(2, (".IoCallDriver(DevObj=%p,Irp=%p)\n", DevObj, Irp));
|
|
status = IoCallDriver(DevObj, Irp);
|
|
EXIT(2, (".IoCallDriver=%x\n", status));
|
|
}
|
|
|
|
if (status == STATUS_PENDING)
|
|
{
|
|
status = KeWaitForSingleObject(&event,
|
|
Executive,
|
|
KernelMode,
|
|
FALSE,
|
|
NULL);
|
|
}
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
status = Irp->IoStatus.Status;
|
|
}
|
|
|
|
EXIT(2, ("=%x\n", status));
|
|
return status;
|
|
} //SendSyncIrp
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func NTSTATUS | IrpCompletion | Completion routine for all IRPs.
|
|
*
|
|
* @parm IN PDEVICE_OBJECT | DevObj | Points to the device object.
|
|
* @parm IN PIRP | Irp | Points to an I/O request packet.
|
|
* @parm IN PKEVENT | Event | Points to the event to notify.
|
|
*
|
|
* @rvalue STATUS_MORE_PROCESSING_REQUIRED | We want the IRP back
|
|
*
|
|
*****************************************************************************/
|
|
|
|
NTSTATUS INTERNAL
|
|
IrpCompletion(
|
|
IN PDEVICE_OBJECT DevObj,
|
|
IN PIRP Irp,
|
|
IN PKEVENT Event
|
|
)
|
|
{
|
|
PROCNAME("IrpCompletion")
|
|
|
|
ENTER(2, ("(DevObj=%p,Irp=%p,Event=%p)\n", DevObj, Irp, Event));
|
|
|
|
UNREFERENCED_PARAMETER(DevObj);
|
|
|
|
KeSetEvent(Event, 0, FALSE);
|
|
|
|
/*
|
|
* If the lower driver returned PENDING, mark our stack location as
|
|
* pending also. This prevents the IRP's thread from being freed if
|
|
* the client's call returns pending.
|
|
*/
|
|
if (Irp->PendingReturned)
|
|
{
|
|
IoMarkIrpPending(Irp);
|
|
}
|
|
|
|
EXIT(2, ("=%x\n", STATUS_MORE_PROCESSING_REQUIRED));
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
} //IrpCompletion
|