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 StartReadCmds, is used to start read requests. A different set, in ReadCmds, is used to process read interrupts. RunStartReadCmdsSynch and RunReadCmdsSynch are run as Critical Section routines to process these two sets of commands. The PHDIO_READ_NEXT command is run in a very similar way to PHDIO_WRITE_NEXT, except that it stores the byte that it reads in the next location in the IRP buffer.

Interrupt Handler

Listing 17.3 shows the interrupt service routine for WdmIo, InterruptHandler. Remember that this is a general-purpose service routine. If the user mode controlling application has set up its control fields or commands wrongly, things could go horribly wrong.

An interrupt handler should complete its job as quickly as possible. It is run at Device IRQL (DIRQL), so do not make DebugPrint calls. Remember that your device could interrupt at any time, not just when you have started a write request.

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 InterruptReg. As described in Chapter 15, it then ANDs this value with InterruptRegMask and compares it with InterruptRegValue. If they are equal, the interrupt is valid and the handler can continue.

If the device extension Timeout flag is –1, no transfer is in progress and so TRUE is returned straight away. If your device requires further processing to cancel such 'spurious' interrupts, you need to amend WdmIo so that it does what you want.

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 Cancel flag.

If the IRP is still in progress, the TxIsWrite flag indicates whether the Read or Write stored commands should be run. The RunWriteCmdsSynch and RunReadCmdsSynch routines return TRUE if the transfer is now complete (i.e., if all the bytes have been transferred or there has been some error).

If the transfer is now complete, this is remembered by setting Timeout to –1. Interrupt routines run at Device IRQL and so cannot complete IRPs. Completing an IRP also takes some time, so this job ought not to be done in the interrupt handler, anyway. The driver model uses Deferred Procedure Calls (DPCs) to solve these problems, as described in the next section. InterruptHandler calls IoRequestDpc to request that the WdmIo DPC be run.

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.

Using Basic DPCs

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 IoInitializeDpcRequest, passing the name of your DPC routine. WdmIo makes this call in Pnp.cpp just after its FDO is created in its AddDevice routine, passing the name of its DPC routine, WdmIoDpcForIsr.

// 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, WdmIoDpcForIsr starts by indicating that the transfer has stopped by setting the device extension Timeout field to –1. It then works out how many bytes have been transferred by subtracting the TxLeft field from TxTotal. If the I/O Manager wants the IRP cancelled, store STATUS_CANCELLED in the TxStatus return status field.

WdmIoDpcForIsr then removes the cancel routine, unlocks the device, completes the IRP, and starts the next queued IRP.

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);

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату
×