3.2.3 Initializing Driver-specific Device Objects and Device Extensions
After IoCreateDevice returns, giving the caller a pointer to a DeviceObject that contains a pointer to the DeviceExtension, the driver can initialize any Kernel-defined objects and other system-defined data structures for which it has provided storage in the device extension.
For example, a lowest-level driver would call IoConnectInterrupt with the address of the PtrToInterruptObject(s) shown in Figure 3.2 to register its ISR, as well as Kernel-supplied support routines to initialize the DpcObject and TimerObject in order to set up a CustomTimerDpc routine. Such a driver would also call KeInitializeSpinLock, passing a pointer to the ExecutiveSpinLock, so the driver could call ExInterlockedInsertTailList and ExInterlockedRemoveHeadList to manage the queueing of IRPs in the interlocked work queue shown in Figure 3.2.
For general guidelines on initializing and using spin locks, see Chapter 16. For more information about interrupt, DPC, and timer objects, see Sections 3.5 and 3.7 later in this chapter. See also Sections 3.6 and 3.9 for other ways to use DPC and timer objects.
NT drivers also must set up certain fields in the device object(s) for their respective physical, logical, and/or virtual devices.
IoCreateDevice sets the StackSize field of a newly created device object to one. A lowest-level NT driver can ignore this field. After any higher-level driver has chained itself over another driver by successfully calling IoGetDeviceObjectPointer, the higher-level driver must set the StackSize field to that of the next-lower-level driver’s device object plus one. This ensures that IRPs sent to the higher-level driver will contain a driver-specific I/O stack location, plus the correct number of I/O stack locations for all lower-level drivers in the chain. If a higher-level driver calls IoAttachDevice or IoAttachDeviceToDeviceStack, those routines automatically set the StackSize field in the device object to the appropriate value.
IoCreateDevice sets the AlignmentRequirement field of a newly created device object to the processor’s data cache line size minus one. To ensure that I/O buffers are aligned correctly, lowest-level physical device drivers must do the following:
1.Subtract one from the alignment requirement of the device.
2.Compare the result of Step 1 with the current value of the device object’s AlignmentRequirement.
3.If the device’s alignment requirement is greater, set AlignmentRequirement to the result of Step 1. Otherwise, leave the AlignmentRequirement value as set by IoCreateDevice.
After any higher-level driver has chained itself over another driver by calling IoGetDeviceObjectPointer, the higher-level driver must set the AlignmentRequirement field of its newly created device object to that of the next-lower-level driver’s device object. If a higher-level driver calls IoAttachDevice or IoAttachDeviceToDeviceStack, those routines automatically set the AlignmentRequirement field in the device object to that of the lower-driver’s device object.
IoGetDeviceObjectPointer returns pointers both to the lower driver’s device object and to the associated file object. Only an FSDs (or, possibly, other highest-level drivers) can use the returned file object pointer. An intermediate driver that calls IoGetDeviceObjectPointer should save this file object pointer so it can be dereferenced by calling ObDereferenceObject when the driver is unloaded.
After an FSD mounts the volume containing the file object that represents a lower driver’s device object, an NT intermediate driver cannot chain itself between the file system and the lower driver by calling IoAttachDevice.
If the caller creates more than one device object, the I/O Manager links its subsequently created device objects to the input DriverObject by maintaining NextDevice pointers in the device objects.
An intermediate or lowest-level NT driver also sets a bit in the device object’s Flags by ORing it either with DO_DIRECT_IO or with DO_BUFFERED_IO, as shown in Figure 3.2, in every device object it creates. Highest-level drivers of logical or virtual devices can avoid setting Flags for either buffered or direct I/O if the driver writer decides the additional work involved will pay off in better driver performance. An NT intermediate driver must set up the Flags field of its device object to match that of the next-lower driver’s device object.
Setting up a device object Flags field with DO_DIRECT_IO or DO_BUFFERED_IO determines how the I/O Manager passes access to user buffers in all data transfer requests subsequently sent to the driver.