Книга: Writing Windows WDM Device Drivers

I/O Request Packets

I/O Request Packets

Dispatch Routine Handling

All dispatch routines have the same function prototype. The function is passed a pointer to your device object and the IRP. The function must return a suitable NTSTATUS value (e.g., STATUS_SUCCESS).

NTSTATUS Wdm1Read( IN PDEVICE_OBJECT fdo. IN PIRP Irp)

A dispatch routine is usually called at PASSIVE_LEVEL IRQL. (This is a bit unexpected, as you might expect dispatch routines to run at DISPATCH_LEVEL IRQL.) Running at PASSIVE_ LEVEL means that a dispatch routine can very easily be interrupted by other parts of the kernel or even your own driver. However, a routine that runs at PASSIVE_LEVEL can issue most kernel calls, including writing to other files.

A dispatch routine can be called at DISPATCH_LEVEL if a higher level driver calls you at this IRQL level. This might happen if it calls you from a completion routine, as described in Chapter 9. If you think this might be happening, then assume that your driver dispatch routines are running at DISPATCH_LEVEL

Dispatch routines run in an arbitrary thread context. As well as standard user mode threads, the kernel has its own threads that run only in kernel mode. Although your IRP will have originated in one of these threads, you cannot guarantee that you arc running in its context. This means that a valid address in the originating thread may not be valid when your driver sees it. A later section in this chapter shows how to access user buffers correctly.

Reentrancy

A dispatch routine must be reentrant. This means that it may be called "simultaneously" to process two separate IRPs. In a multiprocessor Windows 2000 system, one processor could call Wdm1Read with one IRP, and a second process on another CPU could also call Wdm1Read simultaneously with another IRP. However, do not dismiss reentrancy as some arcane requirement that your driver will never encounter. In Windows 98, the single processor could start running Wdm1Read to handle a first IRP. Wdm1Read could issue a kernel request that blocks its operation[14]. Windows 98 might then schedule a different user application that issues another read resulting in another call to Wdm1Read.

The first technique to make your routine reentrant is to use local variables. Each separate call to Wdm1Read definitely has its own separate set of variables on the kernel stack.

Do not use global variables or variables in the device extension unless you protect access to them. However, when these variables are first set up (DriverEntry or AddDevice), you do not need to take special precautions.

The Wdm1 driver uses a spin lock to protect access to the shared memory buffer variables, as described later.

There are two other main techniques to achieve reentrancy in dispatch routines. The first is to use the services of the I/O Manager to create a device queue of IRPs. The IRPs in the device queue are passed one at a time to a StartIo routine in your driver.

The second technique is to use Critical section routines if your device interrupts the computer, as explained in full in Chapter 16. Calling KeSynchronizeExecution runs your Critical section routine safely. KeSynchronizeExecution raises the IRQL to the interrupt IRQL and acquires the interrupt spin lock. This technique ensures that your routine is run to completion without being interrupted by another part of the driver.

IRP Handling

There are three main techniques for handling IRPs.

• Handle immediately

• Put in a queue and process one by one

• Pass down to a lower-level driver

Only the simplest drivers, like Wdm1, can handle IRPs straightaway. Even so, you must still take precautions to ensure that your driver dispatch routines are reentrant.

Drivers that access real pieces of hardware usually want to serialize access to the hardware. As mentioned previously, the I/O Manager in the kernel provides a device queue that you can use. Dispatch routines call the kernel to put an IRP into the device queue. The I/O Manager calls your StartIo routine to process one IRP at a time. When your StartIo routine has completed an IRP, it should call the kernel to ensure that it is called again with the next available IRP.

Drivers might want to use more than one device queue. For example, a serial port driver will usually want to have two device queues; one for incoming read data and one for outgoing write data.

A driver can use a different queuing strategy. For example, the DebugPrint driver described in Chapter 14 lets only one read IRP be queued. Any further read IRPs are rejected while the first read IRP is outstanding.

The final main technique for handling IRPs is to pass them down for handling in a lower-level driver. This approach is common in WDM device drivers. Some drivers simply pass the handling of some IRPs to the next lower driver and forget about them. If you take a more active interest, you can inspect the results after all the lower-level drivers have handled the IRP. These techniques are covered later.

A variant on this theme is for a driver to build a new IRP (or IRPs), send it down to lower drivers, and process the results afterwards.

IRP Completion

When a driver has finished working on an IRP, it must tell the I/O Manager. This is called IRP completion. As this code snippet shows, you must set a couple of fields in the IRP IoStatus field structure. IoStatus.Status is set to an NTSTATUS status code. The number of bytes transferred is usually stored in IoStatus.Information.

Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = BytesTxd;
IoCompleteRequest(Irp,IO_NO_INCREMENT);

Finally, IoCompleteRequest is called (at or below DISPATCH_LEVEL IRQL). As well as the IRP pointer, you must supply a PriorityBoost parameter to give a boost to the scheduling priority of the thread that originated the IRP. For example, keyboard drivers use the constant IO_KEYBOARD_INCREMENT. This is a high value of 6 because foreground threads should respond quickly to user input.

If a dispatch routine does not process an IRP immediately, it must mark the IRP as pending using IoMarkIrpPending and return STATUS_PENDING. When you eventually get round to completing the IRP, do it as described above.

If an IRP is queued and its associated process dies unexpectedly (or calls CancelIo to cancel overlapped IRPs), these pending IRPs must be cancelled. You must set an IRP's cancel routine to handle this circumstance, and to handle the Cleanup IRP. Chapter 16 describes these options in full. Wdm1 does not queue IRPs, so handling these options is not necessary.

Оглавление книги


Генерация: 0.099. Запросов К БД/Cache: 0 / 0
поделиться
Вверх Вниз