if (QueueEntry!=NULL) {
PIRP Irp = CONTAINING_RECORD(QueueEntry, IRP, Tail.Overlay.DeviceQueueEntry);
dx->AbcCurrentIrp = Irp;
IoReleaseCancelSpinLock(OldIrql);
AbcStartIo2(DeviceObject, Irp);
} else {
dx->AbcCurrentIrp = NULL;
IoReleaseCancelSpinLock(OldIrql);
}
}
VOID AbcStartIo2(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
PABC_DEVICE_EXTENSION dx = (PABC_DEVICE_EXTENSION)fdo->DeviceExtension);
PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);
KIRQL Oldlrql;
IoAcquireCancelSpinLock(&OldIrql);
if (Irp->Cancel) {
IoReleaseCancelSpinLock(OldIrql);
return;
}
IoSetCancelRoutine(Irp, NULL);
IoReleaseCancelSpinLock(OldIrql);
// Process and complete request
// …
// Start next packet
AbcStartNextPacket2(DeviceObject);
}
IRP Cancelling and Cleanup
Cancelling IRPs in supplemental device queues can use exactly the same techniques as the standard device queue. In this example, the cancel routine is removed when
The Cleanup IRP handling should use exactly the same techniques as the WdmIo driver. The WdmIo driver uses a
Listing 16.9 Supplemental device queue IRP cancel routine
VOID AbcCancelIrp(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
PABC_DEVICE_EXTENSION dx = (PABC_DEVICE_EXTENSION)fdo->DeviceExtension;
if (Irp=dx->AbcCurrentIrp) {
IoReleaseCancelSpinLock(Irp->CancelIrq1);
CompleteIrp(Irp, STATUS_CANCELLED);
AbcStartNextPacket2(DeviceObject, TRUE);
} else {
KeRemoveEntryDeviceQueue(&dx->AbcIrpQueue, &Irp->Tail.Overlay.DeviceQueueEntry);
IoReleaseCancelSpinLock(Irp->CancelIrql);
CompleteIrp(Irp, STATUS_CANCELLED);
}
}
Conclusion
This chapter has shown how to queue IRPs for serial processing in a driver's
The next chapter inspects the next part of the WdmIo and PHDIo drivers, how to handle interrupts and do interrupt-driven programmed I/O. It also looks at timers, both for IRP time-outs in the order of seconds, and custom timers for finer grain intervals.
Chapter 17
Interrupt-Driven I/O
This chapter shows how the WdmIo and PHDIo drivers handle interrupts, use Deferred Procedure Calls (DPCs), and catch time-outs. It shows how Read and Write interrupt-driven I/O is started and demonstrates how interrupt handling routines should work.
A driver's interrupt handler must service a hardware device's immediate needs. Any major processing must wait until a driver's DPC routine runs. Eventually, the DPC completes the read or write request and starts the next queued IRP.
The chapter also covers two types of timer. A basic 'one-second interval' timer is best used to implement a time-out for interrupt-driven operations. However, custom timers and their associated Custom DPCs can be used for finer grain timing.
Interrupt Handling
First, let me be clear exactly what a hardware interrupt is. Devices that generate interrupts use an electrical signal to indicate that some special condition has occurred. The interrupt system is designed to stop the processor (or one of the processors) in its tracks — as soon as possible — and start some code to service the interrupt.
When an interrupt occurs, the processor saves a small amount of information on the kernel stack: the processor registers and the instruction pointer before the interrupt. This is just enough to restore the context when the interrupt service routine has completed.
Interrupts are usually used when something important has happened, or when a driver ought to do something important to its hardware device. Following are some example interrupt events.
• From the modem: I have just received a character. Come and get it! Another might arrive soon and overwrite this one.
• From the disk controller: I have just finished a DMA transfer of a sector of data. What shall I do now?
• From the printer: I have just printed a character. Give me another!
• From the printer: I have just run out of paper. Please tell the user to buy some more!
As you can see, not all interrupts are equally important. Of the previous four situations, the modem and disk controller interrupts are the most important.
x86 processors have a NonMaskable Interrupt (NMI) pin input and an Interrupt (INTR) pin input, with all normal device interrupts sharing the INTR input. External 8259A controllers are used to provide several interrupt lines to devices (i.e., IRQ0-IRQ15). IRQ0-IRQ15 share the INTR pin. An interrupt vector specifies the memory location that contains the address of the interrupt service routine. The 8259A controllers provide a different vector for each IRQ number. Therefore, IRQ0-IRQ15 each have their own interrupt service routines inside the Windows kernel.
x86 processors prioritize their interrupts in a simple way. Processor exceptions have the highest priority. NMI interrupts come next and then INTR interrupts. The INTR interrupts are maskable, as they can be disabled by setting a bit in the processor status register.
If an INTR interrupt occurs, its service routine will run until completion, stopping other INTR interrupts from occurring. However, an NMI interrupt could still butt in half-way through the INTR service routine. An interrupt service routine should do its job quickly, as it could stop other equally important INTR interrupts from being serviced. The interrupt latency is the time between a device asserting its interrupt signal and its service routine starting. Help keep the interrupt latency down for all drivers by making your interrupt service routine run quickly.