processing by the StartIo routine. The initial processing of IRPs 2 and 3 can happen simultaneously. However, the StartIo routine definitely processes the IRPs serially, one after the other.

Figure 16.1 IRP queuing

Hardware Access

Before I start in earnest, note that the WdmIo driver is a standard WDM driver in a device stack. It is layered over the Unknown bus driver. It does not use the Unknown driver to access hardware. Instead, it talks to hardware directly.

The StartDevice, RetrieveResources, and StopDevice routines in DeviceIo.cpp have been altered slightly from their originals in Wdm2. They insist that one port or memory resource be allocated. If necessary a memory-mapped set of registers is mapped into memory, and unmapped when the device is stopped. Similarly, if WdmIo has connected to an interrupt, StopDevice disconnects and StartDevice connects again.

The WriteByte and ReadByte routines are shown in Listing 16.1. They both fail silently if the register offset is out of range. The DebugPrint trace calls should not be used in these routines, as they may be called at device IRQL (DIRQL) (i.e., above the maximum IRQL suitable for DebugPrint calls, DISPATCH_LEVEL).

If you need to delay for a short period, use the KeStallExecutionProcessor routine, specifying the stall period in microseconds. The DDK recommends that you keep the delay as short as possible, and definitely no more than 50µs. Longer delays usually mean writing code in a very different way. For example, you could use custom timers, as described in the next chapter. Alternatively, a system thread could use the KeDelayExecutionThread function for longer delays.

Listing 16.1 WriteByte and ReadByte

void WriteByte(IN PWDMIO_DEVICE_EXTENSION dx, IN ULONG offset, IN UCHAR byte) {

 if (offset>=dx->PortLength) return;

 PUCHAR Port = dx->PortBase+offset;

 if (dx->PortInIOSpace) WRITE_PORT_UCHAR(Port, byte);

 else WRITE_REGISTER_UCHAR(Port, byte);

}

UCHAR ReadByte(IN PWDMIO_DEVICE_EXTENSION dx, IN ULONG offset) {

 if (offset>=dx->PortLength) return 0;

 PUCHAR Port = dx->PortBase+offset;

 UCHAR b;

 if (dx->PortInIOSpace) b = READ_PORT_UCHAR(Port);

 else b = READ_REGISTER_UCHAR(Port);

 return b;

}

Finally, note that the WdmIo driver read and write dispatch routines and the IOCTLs all use Buffered I/O.

IRP Queuing

Device Queues

The DebugPrint in Chapter 14 used doubly-linked lists as a means of storing blocks of memory for later processing. The WdmIo driver uses the built-in IRP device queue to serialize the processing of its main IRPs. A device queue is a doubly-linked list with special features that tailor it for IRP processing. One of its special features is that it has a built-in spin lock. Thus, a driver does not need to provide its own spin lock to guard access to the queue, as DebugPrint had to do for its doubly-linked lists.

The WdmIo Functional Device Object (FDO) contains a device queue field that stores the queue's linked list head. Each IRP also contains a linked list entry that is used to store the pointers to its linked IRPs. For the moment, the names of these fields do not matter, as most ordinary queue actions are handled internally by the I/O Manager. However, to cleanup the device queue, these entries must be manipulated directly. The I/O Manager also initializes the device queue.

Inserting IRPs into the device queue is actually fairly straightforward. This except from the Read IRP dispatch handler in Dispatch.cpp shows the necessary steps. First, the IRP must be marked as pending using IoMarkIrpPending. Then, IoStartPacket is called to insert the IRP into the queue. If there are no queued IRPs, the IRP is sent for processing in the StartIo routine straightaway. Finally, the Read IRP returns STATUS_PENDING to confirm that the IRP is indeed pending.

IoMarkIrpPending(Irp);

IoStartPacket(fdo, Irp, 0, WdmIoCancelIrp);

return STATUS_PENDING;

The call to IoStartPacket passes a pointer to the cancel routine. I shall show later how this works. The third parameter to IoStartPacket is a key that can be used to sort the IRPs in the device queue. This feature is not used by WdmIo, so the key is always zero.

The WdmIo driver still uses the Plug and Play device locking technique to ensure that IRP processing is not interrupted by Plug and Play stop device requests. Note that the UnlockDevice routine is not called when an IRP is queued. This is deliberate, as the IRP has not been completed. I ensure that UnlockDevice is called on all paths that will later complete the IRP.

The main Read, Write, and IOCTL dispatch routines in WdmIo perform device locking and parameter checking. All valid IRPs of these types are put in the device queue for processing later serially in the WdmIo StartIo routine.

StartIo Routines

The WdmIoStartIo routine shown in Listing 16.2 processes IRPs for each WdmIo device one by one[37]. All queued IRPs, whatever their major function code, come through this same routine. Therefore, StartIo routines usually have a big switch statement at their heart. Listing 16.2 shows the complete code for IOCTL IRPs with major function code IRP_MJ_DEVICE_CONTROL However, the code for Read and Write IRPs is not shown for now. This is explained in the next chapter.

When an IRP is passed to the StartIo routine, it has just been removed from the device queue. The I/O Manager has also put the IRP pointer in the FDO CurrentIrp field. This is particularly useful for interrupt handlers as will be seen in the chapter, but is also used by IRP cancel and cleanup routines. The IRP Cancel field is set to TRUE when the IRP is cancelled. The IRP Cancel field may even be set before StartIo is called. Both WdmIoStartIo and the interrupt handling routines check the Cancel field at various points, as shown later.

StartIo routines are always called at DISPATCH_LEVEL IRQL. This means that all the code and the variables it accesses must be in nonpaged memory. It restricts the set of kernel calls that can be made. However, calls to the DebugPrint trace output routines can be made safely. WdmIoStartIo processes IRPs in two different ways. All the IOCTL IRPs are handled straightaway; the IRP is processed in the relevant way and is completed at the end of WdmIoStartIo. However, Read and Write IRPs use interrupt driven I/O to transfer one byte at a time. These IRPs are usually completed later, as described in the next chapter. In this case, WdmIoStartIo simply returns; the current IRP is still pending and is still being processed.

WdmIoStartIo is only called to process another IRP in the device queue when IoStartNextPacket is called (or IoStartNextPacketByKey). For IRPs that are processed entirely by WdmIoStartIo, IoStartNextPacket is called after the IRP is completed[38]. The TRUE second parameter to IoStartNextPacket indicates that cancel routines are being used.

WdmIoStartIo begins by zeroing the CmdOutputCount field in the device extension. This field stores the count of bytes transferred during immediate IRP processing. CmdOutputCount is passed to CompleteIrp at the end of WdmIoStartIo.

WdmIoStartIo then stops the device timer if the device extension StopTimer field is set to true. This timer is used to detect read and write time-outs and its use is described in the next chapter.

WdmIoStartIo now contains the obligatory huge switch statement, switching on the IRP stack major function code. As stated earlier, only IOCTL, read, and write requests should reach WdmIoStartIo. The IOCTL handler has a subsidiary large switch statement, this time switching on the IOCTL control code. All the IOCTLs are processed straightaway and eventually fall through to complete the IRP at the end of WdmIoStartIo.

WdmIoStartIo ends by completing IRPs that have finished their processing. The Cancel flag is checked and the cancel routine is removed. Finally, the device is unlocked using UnlockDevice, the IRP is completed, and IoStartNextPacket is called.

Listing 16.2 WdmIoStartIo routine

VOID WdmIoStartIo(IN PDEVICE_OBJECT fdo, IN PIRP Irp) {

 PWDMO_DEVICE_EXTENSION dx = (PWDMIO_DEVICE_EXTENSION)fdo->DeviceExtension;

 PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocationIrp);

 PUCHAR Buffer = (PUCHAR)Irp->AssociatedIrp.SystemBuffer;

 // Zero the output count

 dx->CmdOutputCount = 0;

 DebugPrint('WdmIoStartIo: %I', Irp);

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

0

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

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