3.1 NT Components and NT Drivers
Figure 3.1 summarizes the relationship between a chain of layered NT drivers and the NT components whose support routines these drivers can call.
See also the Kernel-Mode Driver Reference for specifics about the support routines that are useful to NT device and intermediate drivers.
Figure 3.1 Executive Component Support for Drivers
The number of drivers in a chain of layered NT drivers depends somewhat on the nature of the device, as already described in Chapter 2. For example, system drivers for SCSI mass-storage devices form at least triple-driver chains, with the NT SCSI class and port drivers corresponding to the intermediate class and lowest-level physical device drivers shown in Figure 3.1, and with at least one NT file system driver layered over each class driver. Within NT, an HBA-specific SCSI miniport driver is considered part of the NT SCSI port driver.
In addition, the system-supplied fault tolerant disk driver, ftdisk, might be layered between the SCSI disk class driver and file system driver(s) if the user decides to enable partition mirroring or the creation of stripe or volume sets. The NT ftdisk driver is optional, but it corresponds to the logical/virtual intermediate device driver shown in Figure 3.1.
By contrast, the system sound and serial drivers described in Chapter 2 are not part of an NT driver chain. Each of these drivers corresponds to the lowest-level physical device driver shown in Figure 3.1. Each is an NT lowest-level device driver with respect to its set of standard driver routines. Each is also a highest-level NT driver with respect to its position relative to the I/O Manager and to user-mode protected subsystems.
Support Routines Manipulate NT Objects
NT lowest-level device drivers have different standard driver routines from higher-level drivers, so Figure 3.1 shows certain objects (interrupt, controller, and adapter) close to the hardware and to the lowest-level drivers that can use these objects. The NT HAL supplies platform-specific support to the I/O Manager, the Kernel, and to most NT device drivers. When a lowest-level device driver initializes, it calls HalGetInterruptVector to obtain platform-specific arguments (system vector, DIRQL, and processor mask) so the driver can call IoConnectInterrupt to set up interrupt object(s) and register its ISR. If its device uses DMA, such a driver supplies configuration-specific information, such as the interface type of the I/O bus to which its device is connected, and calls HalGetAdapter to obtain a pointer to the appropriate adapter object.
During initialization, all NT drivers can call support routines provided by the Configuration Manager to get (and supply) information in the registry. Drivers also can call routines supplied by the I/O Manager to set up symbolic links between their named device objects and the subsystem-specific names for devices to be stored in the registry. NT device drivers also can call IoXxx and/or RtlXxx support routines to get and set configuration information in the registry. For more information about using the registry during driver initialization, see Chapter 16.
Like the I/O Manager, the NT Security Reference Monitor, Configuration Manager, Object Manager, Process Structure, Memory Manager, Executive Support, and Kernel components each define a set of opaque object types and/or data structures, as shown in Figure 3.1. NT drivers can use these objects and data structures only by calling the appropriate kernel-mode support routines.
As Figure 3.1 implies, NT device and intermediate driver writers can ignore the NT LPC (local procedure call) component. Only certain highest-level drivers call support routines supplied by the Security component. The I/O Manager or an NT file system driver performs any necessary interprocess communication and security access checks before an IRP is sent to lower-level NT drivers. The I/O Manager and Object Manager also perform symbolic link resolutions before sending IRPs to lower-level NT drivers.
As Figure 3.1 also shows, the I/O Manager provides support routines for manipulating certain objects and data structures that are defined by other NT components, including symbolic link objects, interrupt objects, DPC objects, timer objects and memory descriptor lists (MDLs). These IoXxx support routines help to implement certain standard NT driver routines, and to map buffers, which can be associated with driver-customized IRPs sent to lower-level drivers. For most NT driver writers, the IoXxx support routines are all their drivers need to do any of the following:
·Register a standard ISR by calling IoConnectInterrupt and deregister the ISR when the driver unloads by calling IoDisconnectInterrupt
·Register a standard DpcForIsr routine by calling IoInitializeDpcRequest and request a call to the driver's DpcForIsr routine from its ISR by calling IoRequestDpc
·Register a standard IoTimer routine by calling IoInitializeTimer and enable or disable once-per-second calls to the IoTimer routine by calling IoStartTimer or IoStopTimer, respectively
·Break a too large buffer, already mapped by an MDL associated with an incoming IRP, into smaller mapped buffers by calling IoBuildPartialMdl in order to carry out DMA operations on a device with limited data transfer capabilities
Except for NT file system drivers, the I/O Manager also sets up an associated device queue object for each device object that NT drivers create when they call IoCreateDevice. It also provides I/O support routines that drivers call to have IRPs routed to their StartIo routines. The IoStartPacket and IoStartNextPacket routines call the Kernel's device queue support routines on a driver's behalf.
However, an NT driver can call many of the same Kernel support routines as the I/O Manager does if the driver designer finds doing this necessary. Depending on the driver's design or on the device, an NT driver can do any of the following:
·Set up a timer object and an associated DPC object that can be explicitly queued as a CustomTimerDpc routine
For example, a driver might have a CustomTimerDpc, rather than an IoTimer routine, if its device requires variable time-out intervals or an interval that must be finer-grained than once per second.
·Set up one or more DPC objects and explicitly queue driver-supplied CustomDpc routines from the ISR
For example, a serial driver might queue a CustomDpc that cancels pending I/O requests when its ISR detects a device error.
·Set up a timer object on which a device-dedicated thread or worker-thread callback routine can wait for a driver-specified interval
Drivers that set up their own device-dedicated system threads, such as the floppy driver described in Chapter 2, also can have their threads wait on Kernel-defined event, semaphore, or, possibly, mutex objects, as can highest-level drivers' worker-thread callback routines. NT drivers can set up a device-dedicated thread by calling PsCreateSystemThread and can release such a thread with PsTerminateSystemThread.
·Set up additional device queue objects
For example, the NT SCSI port driver described in Chapter 2 sets up a single device object to represent an HBA, and the I/O Manager routes IRPs through (or from) appropriate NT SCSI class drivers into the device queue associated with that HBA device object. However, the NT SCSI port driver sets up an additional device queue object for each logical unit on an HBA-specific SCSI bus that is claimed by a SCSI class driver. The NT SCSI port driver uses these additional device queues to sort incoming IRPs into LU-specific device queues.
Because every DriverEntry routine is called in the context of a system thread, a higher-level NT driver can also wait on a Kernel event object when it initializes, after the driver has chained itself above the next-lower-level driver by calling IoGetDeviceObjectPointer and getting a pointer to the lower driver's device object or by calling IoAttachDevice to alias its device object to a lower driver's device object. For example, a class driver's DriverEntry routine might call IoBuildSynchronousFsdRequest or IoBuildDeviceIoControlRequest to set up an IRP with an associated event and, then, pass its driver-allocated IRP on to the underlying port driver with IoCallDriver. Such a class driver could wait on the event by calling KeWaitForSingleObject while the port driver below it gathers (or sets) device-state information that the class driver needs to complete its initialization.
The Executive Support component, shown in Figure 3.1 between the Kernel and Memory Manager components, provides certain support routines that require an initialized spin lock, for which the caller must supply the storage. Any Executive Support routine that contains the word "interlocked" requires a caller-supplied spin lock as an argument. The Executive Support component also provides routines for drivers that allocate system-space (pool) memory and for highest-level drivers, such as FSDs, that use system worker threads.
Whether an NT device driver uses the Memory Manager's routines to manipulate MDLs depends somewhat on the nature of the device. Each driver determines whether it uses MDLs to access user buffers when it sets up its device object(s), as explained in Section 3.2, next.
Storage for NT Objects
Drivers that use the I/O Manager's interrupt, DPC, and timer support routines can rely on the I/O Manager to provide storage for any necessary Kernel-defined objects. However, the NT Kernel does not allocate memory on behalf of callers to its support routines. Consequently, any NT component, including any NT driver, that directly calls the KeXxx routines must provide storage for the Kernel-defined objects it uses. For example, any NT driver that has a standard CustomTimerDpc routine must provide storage for the timer and DPC objects it needs to call the Kernel's support routines that manipulate these objects.
Drivers that have an ISR must provide storage for an interrupt object pointer that is returned by IoConnectInterrupt. The I/O Manager provides storage for and initializes the driver's interrupt objects (up to one per processor in an SMP machine). The Kernel actually defines the interrupt object type with an associated interrupt spin lock and supplies KeSynchronizeExecution, which device drivers call with the interrupt object pointer returned by IoConnectInterrupt and with a driver-supplied SynchCritSection routine to guarantee multiprocessor-safe access to data shared between another driver routine and the driver's ISR.
An NT driver can also provide storage for, initialize, and use an executive spin lock, calling KeAcquireSpinLock and KeReleaseSpinLock to manage multiprocessor-safe access to data shared among driver routines other than an ISR. Driver routines that run at IRQL DISPATCH_LEVEL and share state can call KeAcquireSpinLockAtDpcLevel and KeReleaseSpinLockFromDpcLevel, which run faster than KeAcquireSpinLock and KeReleaseSpinLock. Note that a spin lock is not an NT object. A spin lock is, however, an opaque, Kernel-defined synchronization mechanism that uses memory. For guidelines on how to use spin locks and a summary of the default IRQLs at which standard driver routines are called, see Chapter 16.
NT drivers usually allocate storage for their Kernel-defined objects and spin locks (if any) in the device extensions of the device objects they create. Some drivers allocate storage in a controller extension if, like the "AT" disk driver described in Chapter 2, they have a ControllerControl routine. For more information about controller objects, see Section 3.4.
Most NT intermediate and lowest-level drivers use only a device or controller extension to maintain necessary device state and to provide storage for other driver-determined data, such as Kernel-defined objects, spin locks, interlocked queues, and other driver-defined data. However, NT drivers can use the Memory Manager's support routines when they initialize to allocate contiguous or noncached internal buffers if their devices cannot be serviced adequately without using such buffers. For more information about using memory, see Chapter 16.