When the PNP Manager detects hardware, it first references the registry to see which filter drivers will manage the hardware. If necessary (some drivers may have been loaded by the system because of the need for other hardware), it will load these drivers and invoke their AddDevice function. The last AddDevice function creates a device object and connects to the device stack. Thereafter, the PNP Manager assigns I/O resources to all device drivers.
Once the resource allocation is determined, the PNP manager notifies the device by sending a PNP request with the Irp_mn_start_device sub-function code to each device. Usually the filter driver is not interested in this IRP, so they use the Defaultpnphandler method to pass the request down. The functional driver, on the other hand, needs to do a lot of work on this IRP, including allocating and configuring additional software resources and preparing for the operation of the device. This work needs to be done at the Passive_level level and completed after the lower driver finishes processing the IRP.
Distribute Irp_ Mn_start_device This PNP to the function Pnpstartdevicehandler (FDO,IRP) processing in the PNP handler function;
Switch (minorfunction)
{
Case Irp_mn_start_device:
Status = Pnpstartdevicehandler (FDO,IRP);
Break
}
The following is the Irp_ mn_start_device handler function Pnpstartdevicehandler:
NTSTATUS Pnpstartdevicehandler (in Pdevice_object fdo, in Pirp IRP)
{
Pdevice_extension dx= (pdevice_extension) fdo->deviceextension;
Pio_stack_location irpstack = iogetcurrentirpstacklocation (IRP);
NTSTATUS status = Forwardirpandwait (FDO, IRP);
if (! Nt_success (status))
Return Completeirp (IRP, status, Irp->iostatus.information);
Status=startdevice (dx,irpstack->parameters.startdevice.allocatedresourcestranslated);
Return Completeirp (IRP, status, 0);
}
In the Pnpstartdevicehandler processing function we can see that there is a forwardirpandwait function, this function is
In order to gain control after the Irp_mn_start_device request, the dispatch routine waits for a kernel event, which is eventually notified by the low-level driver of the completion of the IRP. Therefore, this auxiliary function is written to perform this forward and wait mechanism, which is a prototype of the function as follows:
NTSTATUS forwardirpandwait (in Pdevice_object fdo, in Pirp IRP)
{
Pwdm2_device_extension dx= (pwdm2_device_extension) fdo->deviceextension;
KEVENT event;
Keinitializeevent (&event, notificationevent, FALSE); <--1
Iocopycurrentirpstacklocationtonext (IRP); <--2
Iosetcompletionroutine (IRP, (Pio_completion_routine) iocompleterequest, (PVOID
&event, True, true, true); <--3
NTSTATUS status = IoCallDriver (DX->NEXTSTACKDEVICE,IRP); <--4
if (status==status_pending)
{
KeWaitForSingleObject (&event, Executive, KernelMode, FALSE, NULL); <--5
Status = irp->iostatus.status; <--6
}
}
1 Keinitializeevent (event, EventType, initialstate) This function is interpreted as follows:
The event is the address of the events object. EventType is an enumeration value that can be either notificationevent or synchronizationevent. Notification events (notification event) have such a feature that when it enters the signal state it will remain in the signal state until you explicitly reset it to a non-signaled state. In addition, when a notification event enters the signal state, all threads waiting on the event are freed. This is similar to the manual reset event in user mode. For synchronous events (synchronization event), the event is reset to a non-signaled state whenever a thread is released. This is the same as the auto-reset event in user mode. The additional action that the KEWAITXXX function performs on the synchronization event object is to reset it to a non-signaled state. The last argument is a Boolean, which is true to indicate that the initial state of the event is signaled, and false to indicate that the initial state of the event is non-signaled.
We create a kernel event object. Keinitializeevent must be called at the passive_level level. Fortunately, PNP requests are always sent on passive_level, so this is exactly what is needed. The event object itself must occupy non-paged memory. Also, in most cases, you can assume that the execution stack is also non-paged.
2 Since we are going to install a completion routine, we must copy the stack parameters down a layer of drivers.
3 Specifies a completion routine so that we can know when the downlevel driver finishes the IRP. We should wait for the completion of the operation to occur, so we must ensure that our completion routines are called. That's why I've specified three flag parameters as true, and they indicate that we want to call Onrequestcomplete in three cases where the IRP is complete properly, encounters an error, is canceled. The context parameter of the completion routine is the address of the event object.
4 IoCallDriver calls the next layer of driver, either a low-level filter driver or the PDO driver itself. The PDO driver performs some processing, either immediately completing the request, or returning status_pending.
5 if IoCallDriver returns to Status_pending, we will call KeWaitForSingleObject to wait forever on the kernel event we established previously. Our completion routines are again controlled when the lower-level driver finishes the IRP and puts the event into a signal state.
6 Here, we capture the final state of the IRP and return it to our callers.
Once we call IoCallDriver, we give up control of the IRP until some code that runs in the context of any thread calls
IoCompleteRequest notifies the IRP that completion, IoCompleteRequest will invoke our completion routines. The completion routines are particularly simple:
NTSTATUS Onrequestcomplete (pdevice_object fdo, pirp Irp, Pkevent Pev)
{
KeSetEvent (PEV, 0, FALSE); <--1
return status_more_processing_required; <--2
}
1. We put the event of blocking forwardandwait into a signal state.
2. By returning status_more_processing_required, we stopped the I/O stack's rollback processing. At this point, any completion routines installed by the upper filter driver are not called, and the I/O Manager stops working on the IRP. This situation is like not having called iocompleterequest at all, except, of course, some of the low-level completion routines that have been called. At this point, the IRP will be in an intermediate state, but our forwardandwait routine will get ownership of the IRP again.