goto fail;
}
GetUChar(reg);
WriteByte(dx, reg, *dx->TxBuffer++);
dx->TxLeft--;
break;
}
Read requests are processed in a very similar way. As Chapter 15 mentioned, one set of stored commands, in
Interrupt Handler
Listing 17.3 shows the interrupt service routine for WdmIo,
An interrupt handler should complete its job as quickly as possible. It is run at Device IRQL (DIRQL), so do not make
The first job of an interrupt handler is to see if the interrupt was generated by the correct device. If it was not, the routine should return FALSE as quickly as possible to let any other chained interrupt service routines have their go. Otherwise it should process the interrupt (at the very least, stop the device from interrupting) and return TRUE.
InterruptHandler reads the device register at
If the device extension
Next, WdmIo's interrupt handler gets the current IRP for this device. I double-check that there is a current IRP and then see if the I/O Manager has signalled that it should be cancelled by setting its
If the IRP is still in progress, the
If the transfer is now complete, this is remembered by setting
Listing 17.3 WdmIo Interrupt Handler
BOOLEAN InterruptHandler(IN PKINTERRUPT Interrupt, IN PWDMIO_DEVICE_EXTENSION dx) {
// See if interrupt is ours
dx->TxLastIntReg = ReadByte(dx, dx->InterruptReg);
if ((dx->TxLastIntReg&dx->InterruptRegMask) != dx->InterruptRegValue) return FALSE;
// If no transfer in progress then no further processing required
if (dx->Timeout==-1) return TRUE;
// See if current IRP being cancelled
PDEVICE_OBJECT fdo = dx->fdo;
PIRP Irp = fdo->CurrentIrp;
if (Irp==NULL) return TRUE;
BOOLEAN TxComplete = Irp->Cancel;
if (!TxComplete) {
// Run relevant set of commands
if (dx->TxIsWrite) TxComplete = RunWriteCmdsSynch(dx);
else TxComplete = RunReadCmdsSynch(dx);
}
// If all done, in error or being cancelled then call DPC to complete IRP
if (TxComplete) {
dx->Timeout = –1;
IoRequestDpc(fdo, Irp, dx);
}
return TRUE;
}
Deferred Procedure Calls
Code that runs at an elevated IRQL needs to run as quickly as possible. An elevated IRQL is any IRQL above DISPATCH_LEVEL (e.g., at Device IRQL in an interrupt service routine). Code that runs at an elevated IRQL cannot make most useful kernel calls.
The Windows kernel helps solve both these problems with Deferred Procedure Call (DPC) routines, that run at DISPATCH_LEVEL When an interrupt service routine has done all the jobs that must be performed, it should request that its DPC routine be run. This DPC routine should continue where the interrupt service routine left off.
A DPC typically either starts another transfer or completes an IRP.
In WdmIo, all the bytes are transferred in the interrupt handler. When the transfer is complete, WdmIo asks that its DPC be run to complete the IRP.
Other drivers may use their DPC routine to do data transfers. While WdmIo could use this technique, it would be slower. Processing the read or write commands in WdmIo is usually fairly quick, so it is simplest to get it over and done with[43].
Direct Memory Access (DMA) is a hardware facility for transferring many bytes of data from place to place without having to be handled by the processor. The DMA controller or the relevant device usually interrupts when the whole transfer has finished. The interrupt service routine usually runs a DPC routine to start its next transfer or complete the IRP. DMA transfers cannot be started at elevated IRQL, so the next transfer must be set up in a DPC.
An indeterminate amount of time elapses between a DPC being requested and when it is run. Do not defer any interrupt servicing to the DPC if data might be lost (e.g., read data being overwritten). If necessary, store any data in the device extension or some other preallocated nonpaged memory.
The Windows kernel makes it easy to use one DPC routine within a driver. If you want to use more than one DPC routine, check out the next section.
A standard device object contains the necessary KDPC object. However, you need to initialize it using
// Initialize our DPC for IRQ completion processing
IoInitializeDpcRequest(fdo, WdmIoDpcForIsr);
Asking for your DPC to be run is very easy — you just call IoRequestDpc, passing an IRP pointer and a context that you want passed to the DPC routine. As shown previously, WdmIo only asks for its DPC routine to be run when it has transferred all bytes or when an error has occurred. In both cases, the current IRP must be completed and the next queued IRP started.
As shown in Listing 17.4,
Listing 17.4 WdmIo Interrupt Deferred Procedure Call handler
VOID WdmIoDpcForIsr(IN PKDPC Dpc, IN PDEVICE_OBJECT fdo, IN PIRP Irp, IN PWDMIO_DEVICE_EXTENSION dx) {
dx->Timeout = –1;
ULONG BytesTxd = dx->TxTotal - dx->TxLeft;
if (Irp->Cancel) dx->TxStatus = STATUS_CANCELLED;
DebugPrint('WdmIoDpcForIsr: Status %x Info %d', dx->TxStatus, BytesTxd);