3.2.1 Defining Device Extensions
For most NT intermediate and physical device drivers, the device extension is the most important data structure associated with a device object. Its internal structure is driver-defined, used to maintain device state information, to provide storage for any Kernel-defined objects or other system resources, such as spin locks, the driver uses, and to hold any data the driver must have resident and in system space to carry out its I/O operations.
The I/O Manager allocates memory for the device extension from resident, system-space memory: that is, IoCreateDevice allocates memory for a device object from nonpaged pool.
Every standard driver routine that is given an IRP is also given a pointer to a device object that represents the target device for an I/O operation, as already mentioned in Chapter 2. Consequently, any driver routine given an IRP can access the corresponding DeviceExtension through this pointer to the appropriate device object. Usually, a DeviceObject pointer is also an input parameter to a lowest-level driver's ISR.
Figure 3.2 shows a representative set of driver-defined data for the device extension of a lowest-level driver's device object. A higher-level NT driver would not provide storage for an interrupt object pointer returned by IoConnectInterrupt and passed to KeSynchronizeExecution and IoDisconnectInterrupt. However, a higher-level driver would provide storage for the timer and DPC objects shown in Figure 3.2 if the driver has a CustomTimerDpc routine. A higher-level NT driver also might provide storage for an executive spin lock and interlocked work queue.
In addition to providing storage for an interrupt object pointer, an NT device driver must supply storage for an interrupt spin lock if its ISR handles interrupts for two or more devices on different vectors or if it has more than one ISR. For more information about registering an ISR, see Section 3.5 later in this chapter.
Most NT drivers find it convenient to store pointers to their device objects in their device extensions, as shown in Figure 3.2.
NT higher-level drivers almost always store pointers to the next-lower-level drivers' device objects in their device extensions. A higher-level driver must pass a pointer to the next-lower driver's device object to IoCallDriver, after it has set up the next-lower driver's I/O stack location in an IRP, as explained in Chapter 2.
Note also that any higher-level NT driver that allocates IRPs for lower-level drivers must specify how many stack locations the new IRPs should have. In particular, if a higher-level driver calls IoMakeAssociatedIrp, IoAllocateIrp, or IoInitializeIrp, it must access the target device object of the next-lower-level driver to read its StackSize value, in order to supply the correct StackSize as an argument to these support routines.
While an NT higher-level driver can read data from the next-lower-level driver's device object through the pointer returned by IoGetDeviceObjectPointer, such a driver must follow these implementation guidelines:
Never attempt to write data to the lower driver's device object.
The only exceptions to the preceding guideline are NT file systems, which set and clear DO_VERIFY_VOLUME in the Flags of lower-level removable-media drivers' device objects.
Never attempt to access the lower driver's device extension for the following reasons:
·There is no safe way to synchronize access to a single device extension between two NT drivers.
·A pair of NT drivers that implement such a back-door communication scheme cannot be upgraded individually, cannot have an intermediate driver inserted between them without changing existing driver source, and cannot be recompiled and moved readily from one Windows NT platform to the next.
To preserve their interoperability with lower-level drivers from one Windows NT platform or version to the next, higher-level NT drivers either must reuse the IRPs given them or must create new IRPs, and they must use IoCallDriver to communicate requests to lower-level drivers.