The whole goal of the layering of device objects which WDM facilitates is that you want to be able to easily pass IRPs from one layer down to the next. Back in Chapter 2, "Basic Structure of a WDM Driver," I discussed how your AddDevice routine would contribute its portion of the effort required to create a stack of device objects with a statement like this one:
pdx->LowerDeviceObject = IoAttachDeviceToDeviceStack(fdo, pdo); |
where fdo is the address of your own device object and pdo is the address of the physical device object (PDO) at the bottom of the device stack. IoAttachDeviceToDeviceStack returns to you the address of the device object immediately underneath yours. When you decide to forward an IRP that you received from above, this is the device object you'll specify in the eventual call to IoCallDriver.
When you pass an IRP down, you have the additional responsibility of initializing the IO_STACK_LOCATION that the next driver will use to obtain its parameters. One way of doing this is to perform a physical copy, like this:
... IoCopyCurrentIrpStackLocationToNext(Irp); status = IoCallDriver(pdx->LowerDeviceObject, Irp); ... |
IoCopyCurrentIrpStackLocationToNext is a macro in WDM.H that copies all the fields in an IO_STACK_LOCATION—except for the ones that pertain to the I/O completion routines—from the current stack location to the next one. In previous versions of Windows NT, kernel-mode driver writers sometimes copied the entire stack location, which would cause the caller's completion routine to be called twice. (Recall that your completion routine pointer goes in the stack location underneath yours.) For an explanation of how this particular trap could bite the unwary developer, see "Secrets of the Universe Revealed! How NT Handles I/O Completion" in The NT Insider (May 1997, vol. 4, no. 3). The IoCopyCurrentIrpStackLocationToNext macro, which is new with the WDM, avoids the problem.
Driver writers that don't care what happens to the IRP after they pass it down often use a shortcut to get around actually copying a stack location. In such a situation, they won't be installing a completion routine—I just said they don't care what happens to the IRP. Refer to Figure 5-8 for an illustration of the timing of events in this case.
Figure 5-8. Passing an IRP down and ignoring its ending status.
There's no reason to spend the machine cycles to copy your stack location to the next location—the one you already have contains the parameters you want the next driver to see as well as whatever completion pointer the driver above you might have specified. You therefore use the following shortcut:
NTSTATUS ForwardAndForget(PDEVICE_OBJECT fdo, PIRP Irp) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension; IoSkipCurrentIrpStackLocation(Irp); return IoCallDriver(pdx->LowerDeviceObject, Irp); } |
The shortcut is in the function (actually a macro) misleadingly named IoSkipCurrentIrpStackLocation. What this macro does is retard the IRP's stack pointer by one position. IoCallDriver will immediately advance the stack pointer. The net effect is to not change the stack pointer. When the next driver's dispatch routine calls IoGetCurrentIrpStackLocation, it will retrieve exactly the same IO_STACK_LOCATION pointer that we were working with, and it will thereby process exactly the same request (same major and minor function codes) with the same parameters.
You'll notice that the array of IO_STACK_LOCATIONs contains an entry at the very bottom that won't be used in this scenario. In fact, if drivers underneath us play the same trick, there might be more than one location that won't be used. That's not a problem, though—it just means that something allocated more stack locations than it needed to. It's not a problem that the stack gets unwound a little bit quicker during completion processing, either. IoCompleteRequest doesn't use any absolute indices or pointers when it unwinds the stack. It just starts at whatever the current location is when it gains control and works its way upward calling completion routines. All the completion routines that got installed will get called, and the then-current stack locations will be the ones that their drivers were expecting to work with.
The explanation of why IoSkipCurrentIrpStackLocation works is so tricky that I thought an illustration might help. Figure 5-9 illustrates a situation in which three drivers are in a particular stack: yours (the functional device object [FDO]) and two others (an upper filter device object [FiDO] and the PDO). In the first picture (a), you see the relationship between stack locations, parameters, and completion routines when we do the copy step with IoCopyCurrentIrpStackLocationToNext. In the second picture (b), you see the same relationships when we use the IoSkipCurrentIrpStackLocation shortcut. In the second picture, the third and last stack location is fallow, but nobody gets confused by that fact.
Figure 5-9. Comparison of copying vs. skipping I/O stack locations.