// Remove cancel routine
KIRQL OldIrql;
IoAcqiureCancelSpinLock(&OldIrql);
IoSetCancelRoutine(Irp, NULL);
IoReleaseCancelSpinLock(OldIrql);
// Unlock device and complete
IRP UnlockDevice(dx);
CompleteIrp(Irp, dx->TxStatus, BytesTxd);
IoStartNextPacket(fdo, TRUE);
// Stop timer calls
dx->StopTimer = true;
}
DPC Gotchas
Even with this simple DPC for deferred interrupt processing, there are some potential problems that you have to look out for.
The first point to note is that if two or more calls to IoRequestDpc are made before the DPC can be run, the DPC is only run once. Suppose two interrupts occur in quick succession. If each interrupt handler calls
For the WdmIo driver, this problem should almost never arise. The only situation I can envision is one in which an IRP is cancelled just after the interrupt handler calls
However, in this case, the late-running DPC routine will find that the IRP has been cancelled, which is correct.
The other potential problems regarding DPCs will only occur in multiprocessor systems.
• A DPC routine is running on one processor as a device interrupt is handled on another.
• When an interrupt handler asks for a DPC to be run, it is run straight away on another processor before the interrupt service routine exits.
• Two or more DPC routines may be running at the same time on different processors.
The main solution to these problems is to use Critical Section routines whenever a DPC routine needs to access fields that an interrupt handler or another DPC use.
If you need to use more than one DPC, this is fairly straightforward. These 'custom DPCs' are also used for Custom Timers with fine grain time-outs.
Declare a KDPC object in nonpaged memory (e.g., in the device extension). Initialize it using
To ask that your DPC routine be run, call
Table 17.2 shows the function prototype for your custom DPC routine. There are three context parameters. To be compatible with the basic DPC handler, these should be your FDO, the IRP, and usually the device extension. However, use these as you wish.
Table 17.2 CustomDpc prototype
VOID CustomDpc | (IRQL==DISPATCH_LEVEL) |
---|---|
Parameter | Description |
IN PKDPC Dpc | DPC |
IN PVOID Context | DeferredContext parameter given to KeInitializeDpc |
IN PVOID SystemArg1 | SystemArgument1 passed to KeInsertQueueDpc (NULL for custom timers) |
IN PVOID SystemArg2 | SystemArgument2 passed to KeInsertQueueDpc (NULL for custom timers) |
Timers
Two different types of timer can be used. A basic timer is called once every second; WdmIo uses this to detect device time-outs. Custom timers may be set up with resolutions starting from 100ns.
The kernel provides easy access to a device timer that calls you back every second. The tinier must be initialized at PASSIVE_LEVEL IRQL using
status = IoInitializeTimer(fdo, (PIO_TIMER_ROUTINE)Timeout1s, dx);
if (!NT_SUCCESS(status)) {
IoDeleteDevice(fdo);
return status;
}
Use
The timer routine is called at DISPATCH_LEVEL. You will usually need to use a Critical Section routine if you wish
Some drivers start their timer calls when a device is created and stop them when it is removed. WdmIo reduces the number of timer callbacks. It starts the timer whenever an interrupt driven I/O starts. When the transfer is completed, it sets a
Listing 17.5 shows the WdmIo timer callback,
The device extension
Timeout1s, therefore, first checks whether there is a transfer in progress. If not (i.e., if
If