Книга: Writing Windows WDM Device Drivers

Interrupt Handling

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.

The Nature of the Beast

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.

As described previously, Windows provides the initial interrupt handler for IRQ0-IRQ15. If your driver has connected to one of these interrupts (IRQ7 in the default case for WdmIo), it calls your interrupt handling routine to service the interrupt. More than one device can share the same interrupt line, so your first job is to determine if it really was your device that interrupted the processor.

Most hardware has a mind of its own, especially when riddled with by users, and so is likely to generate interrupts at the most awkward time. In particular, you can guarantee that the interrupts will not occur in the context of the user mode thread that should receive its data. Note that an interrupt may occur at any point in your driver code, so you have to be especially careful that you can cope.

Naturally, the Windows kernel helps considerably. In particular, Critical Section routines forcibly increase the IRQ level (IRQL) to the appropriate Device IRQL (DIRQL) so that an interrupt cannot intervene while your Critical Section routine is running. In an x86 system, it sets the appropriate bit in the processor status register that disables INTR interrupts.

Most devices that interrupt have some similar sort of interrupt disabling facility. At power up, a device's interrupts are usually disabled. However, it is best to disable your device's interrupts at the first possible opportunity. Once your driver has installed its interrupt handler, you can enable interrupts in the device. Even now, you should note that interrupts may not conveniently arrive after you have started processing your read request. From the word go, a modem might start interrupting with incoming data. Your driver may well not have received a Read IRP yet, so you will have to decide what to do with these incoming bytes. You could buffer them somewhere in the expectation of a Read IRP coming round the corner. Or you could just dump them on the floor.

Connecting to Interrupts

A WDM driver receives its interrupt resource assignments in its Plug and Play IRP StartDevice handler. Each interrupt has these details:

• Interrupt level[42]

• Interrupt vector

• Interrupt affinity: a set of bits indicating which processors it can use in a multiprocessor system

• Interrupt mode: latched or level sensitive

Usually, you just need to store these details and pass them on to IoConnectInterrupt when you are ready to handle interrupts.

First, declare a PKINTERRUPT object that will be initialized by IoConnectInterrupt. Like most drivers, WdmIo declares this in its device extension. However, if all your devices use the same interrupt, this object may well need to be a driver global; you would need to connect to the interrupt only once (e.g., in your DriverEntry routine).

Table 17.1 shows the parameters to pass to IoConnectInterrupt. Apart from the various assigned interrupt values, you must pass the name of your interrupt handling routine and a context to pass to it. The WdmIo driver uses the device extension pointer as this context parameter. If all your devices share an interrupt, you will need to use a different context pointer; the interrupt handler will have to work out for itself to which device the interrupt refers.

The only complication to IoConnectInterrupt is what happens when you use more than one interrupt. You need to call IoConnectInterrupt once for each interrupt, using the same initialized spin lock pointer in each call. This is used to resolve tensions between the various handlers so that only one is called at once. Pass the highest DIRQL value in the SynchronizeIrql parameter in each call.

Table 17.1 IoConnectInterrupt function

Parameter Description
OUT PKINTERRUPT *InterruptObject Interrupt object pointer
IN PKSERVICE_ROUTINE ServiceRoutine Name of interrupt handling routine
IN PVOID ServiceContext Context to pass to the interrupt handler, usually the device extension pointer
IN PKSPIN_LOCK SpinLock Optional spin lock parameter used when a driver uses more than one interrupt; NULL, otherwise
IN ULONG Vector Assigned interrupt vector
IN KIRQL Irql Assigned interrupt IRQL
IN KIRQL SynchronizeIrql The highest IRQL of the interrupts that a driver uses (i.e., usually the same as IRQL)
IN KINTERRUPT_MODE InterruptMode Assigned LevelSensitive or Latched value
IN BOOLEAN ShareVector TRUE if the interrupt vector is shareable
IN KAFFINITY ProcessorEnableMask Assigned interrupt affinity
IN BOOLEAN FloatingSave TRUE if the floating-point stack should be saved For x86 systems, this value must be FALSE

Do not forget to disconnect your interrupt handler using IoDisconnectInterrupt before your device disappears. And you must disconnect from the interrupt if the Plug and Play system stops your device. The WdmIo driver always stops its device when the device is about to be removed, so the StopDevice routine in DeviceIo.cpp is the only place where WdmIo disconnects its interrupt handler.

The WdmIo driver connects to its interrupt handler when it processes a PHDIO_IRQ_CONNECT command in its ProcessCmds routine. It remembers if it connected successfully in the device extension ConnectedToInterrupt field. If the device is stopped for Plug and Play resource reallocation, this flag is left true after IoDisconnectInterrupt is called. When the device is restarted, WdmIo reconnects to the new interrupt if ConnectedToInterrupt is true.

The actual call to IoConnectInterrupt is done in a system worker thread as this function must be called at PASSIVE_LEVEL IRQL. In this case, it is the work item that completes the IRP and starts the next IRP using IoStartNextPacket.

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

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