Summary--Eight IRP-Handling Scenarios

Summary Eight IRP-Handling Scenarios

Notwithstanding the length of the preceding explanations, IRP handling is actually quite easy. By my reckoning, only eight significantly different scenarios are in common use, and the code required to handle those scenarios is pretty simple. In this final section of this chapter, I ve assembled some pictures and code samples to help you sort out all the theoretical knowledge.

Because this section is intended as a cookbook that you can use without completely understanding every last nuance, I ve included calls to the remove lock functions that I ll discuss in detail in Chapter 6. I ve also used the shorthand IoSetCompletionRoutine[Ex] to indicate places where you ought to call IoSetCompletionRoutineEx, in a system where it s available, to install a completion routine. I ve also used an overloaded version of my CompleteRequest helper routine that doesn t change IoStatus.Information in these examples because that would be correct for IRP_MJ_PNP and not incorrect for other types of IRP.

Scenario 1 Pass Down with Completion Routine

In this scenario, someone sends you an IRP. You ll forward this IRP to the lower driver in your PnP stack, and you ll do some postprocessing in a completion routine. See Figure 5-11. Adopt this strategy when all of the following are true:

  • Someone is sending you an IRP (as opposed to you creating the IRP yourself).

  • The IRP might arrive at DISPATCH_LEVEL or in an arbitrary thread (so you can t block while the lower drivers handle the IRP).

  • Your postprocessing can be done at DISPATCH_LEVEL if need be (because completion routines might be called at DISPATCH_LEVEL).

    figure 5-11 pass down with completion routine.

    Figure 5-11. Pass down with completion routine.

Your dispatch and completion routines will have this skeletal form:

NTSTATUS DispatchSomething(PDEVICE_OBJECT fdo, PIRP Irp) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension; NTSTATUS status = IoAcquireRemoveLock(&pdx->RemoveLock, Irp); if (!NT_SUCCESS(status)) return CompleteRequest(Irp, status); IoCopyCurrentIrpStackLocationToNext(Irp); IoSetCompletionRoutine(Irp, (PIO_COMPLETION_ROUTINE) CompletionRoutine, pdx, TRUE, TRUE, TRUE); return IoCallDriver(pdx->LowerDeviceObject, Irp); } NTSTATUS CompletionRoutine(PDEVICE_OBJECT fdo, PIRP Irp, PDEVICE_EXTENSION pdx) { if (Irp->PendingReturned) IoMarkIrpPending(Irp); <whatever post processing you wanted to do> IoReleaseRemoveLock(&pdx->RemoveLock, Irp); return STATUS_SUCCESS; }

Scenario 2 Pass Down Without Completion Routine

In this scenario, someone sends you an IRP. You ll forward the IRP to the lower driver in your PnP stack, but you don t need to do anything with the IRP. See Figure 5-12. Adopt this strategy, which can also be called the Let Mikey try it approach, when both of the following are true:

  • Someone is sending you an IRP (as opposed to you creating the IRP yourself).

  • You don t process this IRP, but a driver below you might want to.

    figure 5-12 pass down without completion routine.

    Figure 5-12. Pass down without completion routine.

This scenario is often used in a filter driver, which should act as a simple conduit for every IRP that it doesn t specifically need to filter.

I recommend writing the following helper routine, which you can use whenever you need to employ this strategy.

NTSTATUS ForwardAndForget(PDEVICE_EXTENSION pdx, PIRP Irp) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension; NTSTATUS status = IoAcquireRemoveLock(&pdx->RemoveLock, Irp); if (!NT_SUCCESS(status)) return CompleteRequest(Irp, status); IoSkipCurrentIrpStackLocation (Irp); status = IoCallDriver(pdx->LowerDeviceObject, Irp); IoReleaseRemoveLock(&pdx->RemoveLock, Irp); return status; }

Scenario 3 Complete in the Dispatch Routine

In this scenario, you immediately complete an IRP that someone sends you. See Figure 5-13. Adopt this strategy when:

  • Someone is sending you an IRP (as opposed to you creating the IRP yourself), and

  • You can process the IRP immediately. This would be the case for many kinds of I/O control (IOCTL) requests. Or

  • Something is obviously wrong with the IRP, in which case causing it to fail immediately might be the kindest thing to do.

    figure 5-13 complete in the dispatch routine.

    Figure 5-13. Complete in the dispatch routine.

Your dispatch routine has this skeletal form:

NTSTATUS DispatchSomething(PDEVICE_OBJECT fdo, PIRP Irp) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension; <process the IRP> Irp->IoStatus.Status = STATUS_XXX; Irp->IoStatus.Information = YYY; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_XXX; }

Scenario 4 Queue for Later Processing

In this scenario, someone sends you an IRP that you can t handle right away. You put the IRP on a queue for later processing in a StartIo routine. See Figure 5-14. Adopt this strategy when both of the following are true:

  • Someone is sending you an IRP (as opposed to you creating the IRP yourself).

  • You don t know that you can process the IRP right away. This would frequently be the case for IRPs that require serialized hardware access, such as reads and writes.

    figure 5-14 queue for later processing.

    Figure 5-14. Queue for later processing.

Although you have many choices, a typical way of implementing this scenario involves using a DEVQUEUE to manage the IRP queue. The following fragments show how various parts of a driver for a programmed I/O interrupt-driven device would interact. Only the parts shown in boldface pertain specifically to IRP handling.

typedef struct _DEVICE_EXTENSION {  DEVQUEUE dqReadWrite; } DEVICE_EXTENSION, *PDEVICE_EXTENSION; NTSTATUS AddDevice(PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT pdo) {  InitializeQueue(&pdx->dqReadWrite, StartIo); IoInitializeDpcRequest(fdo, (PIO_DPC_ROUTINE) DpcForIsr);  } NTSTATUS DispatchReadWrite(PDEVICE_OBJECT fdo, PIRP Irp) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension; IoMarkIrpPending(Irp); StartPacket(&pdx->dqReadWrite, fdo, Irp, CancelRoutine); return STATUS_PENDING; } VOID CancelRoutine(PDEVICE_OBJECT fdo, PIRP Irp) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension; CancelRequest(&pdx->dqReadWrite, Irp); } VOID StartIo(PDEVICE_OBJECT fdo, PIRP Irp) {  } BOOLEAN OnInterrupt(PKINTERRUPT junk, PDEVICE_EXTENSION pdx) {  PIRP Irp = GetCurrentIrp(&pdx->dqReadWrite); Irp->IoStatus.Status = STATUS_XXX; Irp->IoStatus.Information = YYY; IoRequestDpc(pdx->DeviceObject, NULL, pdx);  } VOID DpcForIsr(PKDPC junk1, PDEVICE_OBJECT fdo, PIRP junk2, PDEVICE_EXTENSION pdx) {  PIRP Irp = GetCurrentIrp(&pdx->dqReadWrite); StartNextPacket(&pdx->dqReadWrite, fdo); IoCompleteRequest(Irp, IO_NO_INCREMENT); }

Scenario 5 Your Own Asynchronous IRP

In this scenario, you create an asynchronous IRP, which you forward to another driver. See Figure 5-15. Adopt this strategy when the following conditions are true:

  • You need another driver to perform an operation on your behalf.

  • Either you re in an arbitrary thread (which you shouldn t block) or you re running at DISPATCH_LEVEL (in which case you can t block).

    figure 5-15 your own asynchronous irp.

    Figure 5-15. Your own asynchronous IRP.

You ll have code like the following in your driver. This won t necessarily be in an IRP dispatch routine, and the target device object won t necessarily be the next lower one in your PnP stack. Look in the DDK documentation for full details about how to call IoBuildAsynchronousFsdRequest and IoAllocateIrp.

SOMETYPE SomeFunction(PDEVICE_EXTENSION pdx, PDEVICE_OBJECT DeviceObject) { 

NTSTATUS status = IoAcquireRemoveLock(&pdx->RemoveLock, (PVOID) 42);

if (!NT_SUCCESS(status))

return <status>; PIRP Irp; Irp = IoBuildAsynchronousFsdRequest(IRP_MJ_XXX, DeviceObject, ...); -or- Irp = IoAllocateIrp(DeviceObject->StackSize, FALSE); PIO_STACK_LOCATION stack = IoGetNextIrpStackLocation(Irp); stack->MajorFunction = IRP_MJ_XXX; <additional initialization) IoSetCompletionRoutine[Ex]([pdx->DeviceObject,] Irp, (PIO_COMPLETION_ROUTINE) CompletionRoutine, pdx, TRUE, TRUE, TRUE);

ObReferenceObject(DeviceObject); IoCallDriver(DeviceObject, Irp);

ObDereferenceObject(DeviceObject); } NTSTATUS CompletionRoutine(PDEVICE_OBJECT junk, PIRP Irp, PDEVICE_EXTENSION pdx) { <IRP cleanup -- see below> IoFreeIrp(Irp);

IoReleaseRemoveLock(&pdx->RemoveLock, (PVOID) 42); return STATUS_MORE_PROCESSING_REQUIRED; }

The calls to IoAcquireRemoveLock and IoReleaseRemoveLock (the points labeled A) are necessary only if the device to which you re sending this IRP is the LowerDeviceObject in your PnP stack. The 42 is an arbitrary tag it s simply too complicated to try to acquire the remove lock after the IRP exists just so we can use the IRP pointer as a tag in the debug build.

The calls to ObReferenceObject and ObDereferenceObject that precede and follow the call to IoCallDriver (the points labeled B) are necessary only when you ve used IoGetDeviceObjectPointer to obtain the DeviceObject pointer and when the completion routine (or something it calls) will release the resulting reference to a device or file object.

You do not have both the A code and the B code you have one set or neither.

If you use IoBuildAsynchronousFsdRequest to build an IRP_MJ_READ or IRP_MJ_WRITE, you have some relatively complex cleanup to perform in the completion routine.

Cleanup for DO_DIRECT_IO Target

If the target device object indicates the DO_DIRECT_IO buffering method, you ll have to release the memory descriptor lists that the I/O Manager allocated for your data buffer:

NTSTATUS CompletionRoutine(...) { PMDL mdl; while ((mdl = Irp->MdlAddress)) { Irp->MdlAddress = mdl->Next; MmUnlockPages(mdl); // <== only if you earlier // called MmProbeAndLockPages IoFreeMdl(mdl); } IoFreeIrp(Irp); <optional release of remove lock> return STATUS_MORE_PROCESSING_REQUIRED; }

Cleanup for DO_BUFFERED_IO Target

If the target device object indicates DO_BUFFERED_IO, the I/O Manager will create a system buffer. Your completion routine should theoretically copy data from the system buffer to your own buffer and then release the system buffer. Unfortunately, the flag bits and fields needed to do this are not documented in the DDK. My advice is to simply not send reads and writes directly to a driver that uses buffered I/O. Instead, call ZwReadFile or ZwWriteFile.

Cleanup for Other Targets

If the target device indicates neither DO_DIRECT_IO nor DO_BUFFERED_IO, there is no additional cleanup. Phew!

Scenario 6 Your Own Synchronous IRP

In this scenario, you create a synchronous IRP, which you forward to another driver. See Figure 5-16. Adopt this strategy when all of the following are true:

  • You need another driver to perform an operation on your behalf.

  • You must wait for the operation to complete before proceeding.

  • You re running at PASSIVE_LEVEL in a nonarbitrary thread.

    figure 5-16 your own synchronous irp.

    Figure 5-16. Your own synchronous IRP.

You ll have code like the following in your driver. This won t necessarily be in an IRP dispatch routine, and the target device object won t necessarily be the next lower one in your PnP stack. Look in the DDK documentation for full details about how to call IoBuildSynchronousFsdRequest and IoBuildDeviceIoControlRequest.

SOMETYPE SomeFunction(PDEVICE_EXTENSION pdx, PDEVICE_OBJECT DeviceObject) { 

NTSTATUS status = IoAcquireRemoveLock(&pdx->RemoveLock, (PVOID) 42);

if (!NT_SUCCESS(status))

return <status>; PIRP Irp; KEVENT event; IO_STATUS_BLOCK iosb; KeInitializeEvent(&event, NotificationEvent, FALSE); Irp = IoBuildAsynchronousFsdRequest(IRP_MJ_XXX, DeviceObject, ..., &event, &iosb); -or- Irp = IoBuildDeviceIoControlRequest(IOCTL_XXX, DeviceObject, ..., &event, &iosb); status = IoCallDriver(DeviceObject, Irp); if (status == STATUS_PENDING) { KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); status = iosb.Status; }

IoReleaseRemoveLock(&pdx->RemoveLock, (PVOID) 42); }

As in scenario 5, the calls to IoAcquireRemoveLock and IoReleaseRemoveLock (the points labeled A) are necessary only if the device to which you re sending this IRP is the LowerDeviceObject in your PnP stack. The 42 is an arbitrary tag it s simply too complicated to try to acquire the remove lock after the IRP exists just so we can use the IRP pointer as a tag in the debug build.

We ll use this scenario frequently in Chapter 12 to send USB Request Blocks (URBs) synchronously down the stack. In the examples we ll study there, we ll usually be doing this in the context of an IRP dispatch routine that independently acquires the remove lock. Therefore, you won t see the extra remove lock code in those examples.

You do not clean up after this IRP! The I/O Manager does it automatically.

Scenario 7 Synchronous Pass Down

In this scenario, someone sends you an IRP. You pass the IRP down synchronously in your PnP stack and then continue processing. See Figure 5-17. Adopt this strategy when all of the following are true:

  • Someone is sending you an IRP (as opposed to you creating the IRP yourself).

  • You re running at PASSIVE_LEVEL in a nonarbitrary thread.

  • Your postprocessing for the IRP must be done at PASSIVE_LEVEL.

    figure 5-17 synchronous pass down.

    Figure 5-17. Synchronous pass down.

A good example of when you would need to use this strategy is while processing an IRP_MN_START_DEVICE flavor of PnP request.

I recommend writing two helper routines to make it easy to perform this synchronous pass-down operation:

NTSTATUS ForwardAndWait(PDEVICE_EXTENSION pdx, PIRP Irp) { KEVENT event; KeInitialize(&event, NotificationRoutine, FALSE); IoCopyCurrentIrpStackLocationToNext(Irp); IoSetCompletionRoutine(Irp, (PIO_COMPLETION_ROUTINE) ForwardAndWaitCompletionRoutine, &event, TRUE, TRUE, TRUE); NTSTATUS status = IoCallDriver(pdx->LowerDeviceObject, Irp); if (status == STATUS_PENDING) { KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); status = Irp->IoStatus.Status; } return status; } NTSTATUS ForwardAndWaitCompletionRoutine(PDEVICE_OBJECT fdo, PIRP Irp, PKEVENT pev) { if (Irp->PendingReturned) KeSetEvent(pev, IO_NO_INCREMENT, FALSE); return STATUS_MORE_PROCESSING_REQUIRED; }

The caller of this routine needs to call IoCompleteRequest for this IRP and to acquire and release the remove lock. It s inappropriate for ForwardAndWait to contain the remove lock logic because the caller might not want to release the lock so soon.

Note that the Windows XP DDK function IoForwardIrpSynchronously encapsulates these same steps.

Scenario 8 Asynchronous IRP Handled Synchronously

In this scenario, you create an asynchronous IRP, which you forward to another driver. Then you wait for the IRP to complete. See Figure 5-18. Adopt this strategy when all of the following are true:

  • You need another driver to perform an operation on your behalf.

  • You need to wait for the operation to finish before you can go on.

  • You re running at APC_LEVEL in a nonarbitrary thread.

    figure 5-18 asynchronous irp handled synchronously.

    Figure 5-18. Asynchronous IRP handled synchronously.

I use this technique when I ve acquired an executive fast mutex and need to perform a synchronous operation. Your code combines elements you ve seen before (compare with scenarios 5 and 7):

SOMETYPE SomeFunction(PDEVICE_EXTENSION pdx, PDEVICE_OBJECT DeviceObject) { 

NTSTATUS status = IoAcquireRemoveLock(&pdx->RemoveLock, (PVOID) 42);

if (!NT_SUCCESS(status))

return <status>; PIRP Irp; Irp = IoBuildAsynchronousFsdRequest(IRP_MJ_XXX, DeviceObject, ...); -or- Irp = IoAllocateIrp(DeviceObject->StackSize, FALSE); PIO_STACK_LOCATION stack = IoGetNextIrpStackLocation(Irp); Stack->MajorFunction = IRP_MJ_XXX; <additional initialization) KEVENT event; KeInitializeEvent(&event, NotificationEvent, FALSE); IoSetCompletionRoutine[Ex]([pdx->DeviceObject], Irp, (PIO_COMPLETION_ROUTINE) CompletionRoutine, &event, TRUE, TRUE, TRUE); status = IoCallDriver(DeviceObject, Irp); if (status == STATUS_PENDING) KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);

IoReleaseRemoveLock(&pdx->RemoveLock, (PVOID) 42); } NTSTATUS CompletionRoutine(PDEVICE_OBJECT junk, PIRP Irp, PKEVENT pev) { if (Irp->PendingReturned) KeSetEvent(pev, EVENT_INCREMENT, FALSE); <IRP cleanup -- see above> IoFreeIrp(Irp); return STATUS_MORE_PROCESSING_REQUIRED; }

The portions that differ from scenario 5 are in boldface.

As in earlier scenarios, the calls to IoAcquireRemoveLock and IoRelease RemoveLock (the points labeled A) are necessary only if the device to which you re sending this IRP is the LowerDeviceObject in your PnP stack. The 42 is an arbitrary tag it s simply too complicated to try to acquire the remove lock after the IRP exists just so we can use the IRP pointer as a tag in the debug build.

Note that you must still perform all the same cleanup discussed earlier because the I/O Manager doesn t clean up after an asynchronous IRP. You might also need to provide for cancelling this IRP, in which case you should use the technique discussed in the body of this chapter for cancelling asynchronous IRPs.



Programming the Microsoft Windows Driver Model
Programming the Microsoft Windows Driver Model
ISBN: 0735618038
EAN: 2147483647
Year: 2003
Pages: 119
Authors: Walter Oney

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net