Книга: Writing Windows WDM Device Drivers

IRP Queuing

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);
 // Stop the 1 second timer if necessary
 if (dx->StopTimer) {
  IoStopTimer(fdo);
  dx->StopTimer = false;
 }
 NTSTATUS status = STATUS_SUCCESS;
 // Switch on the IRP major function code
 switch(IrpStack->MajorFunction) {
 //////////////////////////////////////////////////////////////////////
 case IRP_MJ_DEVICE_CONTROL:
  {
   ULONG ControlCode = IrpStack->Parameters.DeviceIoControl.IoControlCode;
   ULONG InputLength = IrpStack->Parameters.DeviceIoControl.InputBufferLength;
   ULONG OutputLength = IrpStack->Parameters.DeviceIoControl.OutputBufferLength;
   switch(ControlCode) {
   ////////////////////////////////////////////////////////////////////
   case IOCTL_PHDIO_RUN_CMDS:
    DebugPrint( "WdmIoStartIo: Run Cmds %s", dx->ConnectedToInterrupt?"(synchronised)":"");
    // Run the commands, synchronized with interrupt if necessary
    if (dx->ConnectedToInterrupt) {
     if (!KeSynchronizeExecution(dx->InterruptObject, (PKSYNCHRONIZE_ROUTINE)RunCmdsSynch, (PVOID)fdo))
      status = STATUS_UNSUCCESSFUL;
    } else if (!RunCmds(fdo, true)) status = STATUS_UNSUCCESSFUL;
    break;
   ////////////////////////////////////////////////////////////////////
   case IOCTL_PHDIO_CMDS_FOR_READ:
    DebugPrintMsg( "WdmIoStartIo: Store cmds for read");
    status = StoreCmds(&dx->ReadCmds, &dx->ReadCmdsLen, InputLength, Buffer);
    break;
   ////////////////////////////////////////////////////////////////////
   case IOCTL_PHDIO_CMDS_FOR_READ_START:
    DebugPrintMsg("WdmIoStartIo: Store cmds for read start");
    status = StoreCmds(&dx->StartReadCmds, &dx->StartReadCmdsLen, InputLength, Buffer);
    break;
   ////////////////////////////////////////////////////////////////////
   case IOCTL_PHDIO_CMDS_FOR_WRITE:
    DebugPrintMsg("WdmIoStartIo: Store cmds for write");
    status = StoreCmds(&dx->WriteCmds, &dx->WriteCmdsLen, InputLength, Buffer);
    break;
   ////////////////////////////////////////////////////////////////////
   case IOCTL_PHDIO_GET_RW_RESULTS:
    // Copy cmd output first
    dx->CmdOutputCount = dx->TxCmdOutputCount;
    if (dx->CmdOutputCount>OutputLength) dx->CmdOutputCount = OutputLength;
    RtlCopyMemory(Buffer, dx->TxResult, dx->CmdOutputCount);
    // Then add on last interrupt reg value
    if (dx->CmdOutputCount+1<=OutputLength) Buffer[dx->CmdOutputCount++] = dx->TxLastIntReg;
    DebugPrint("WdmIoStartIo: Get RW Results: %d bytes", dx->CmdOutputCount);
    break;
   ////////////////////////////////////////////////////////////////////
   default:
    status = STATUS_NOT_SUPPORTED;
   }
   break;
  }
 //////////////////////////////////////////////////////////////////////
 case IRP_MJ_WRITE:
  // …
 case IRP_MJ_READ:
  // …
 default:
  status = STATUS_NOT_SUPPORTED;
  break;
 }
 //////////////////////////////////////////////////////////////////////
 // Complete this IRP
 if (Irp->Cancel) status = STATUS_CANCELLED;
 // Remove cancel routine KIRQL OldIrql;
 IoAcquireCancelSpinLock(&OldIrql);
 IoSetCancelRoutine(Irp, NULL);
 IoReleaseCancelSpinLock(OldIrql);
 // Unlock device, complete IRP and start next UnlockDevice(dx);
 DebugPrint("WdmIoStartIo: CmdOutputCount %d", dx->CmdOutputCount);
 CompleteIrp(Irp, status, dx->CmdOutputCount);
 IoStartNextPacket(fdo, TRUE);
}

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


Генерация: 1.860. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз