3.8.1 Setting up a Device Queue Object and Queueing IRPs
An NT driver can set up a device queue object by calling KeInitializeDeviceQueue when the driver initializes. After the driver is loaded, it inserts IRPs into this queue by calling KeInsertDeviceQueue or KeInsertByKeyDeviceQueue. Figure 3.21 illustrates these calls.
Figure 3.21 Using a Device Queue Object
As Figure 3.21 shows, the driver must provide the storage for a device queue object, which must be resident. Drivers that set up a device queue object usually provide the necessary storage in the device extension of a driver-created device object (see Section 3.2), but it can be in a controller extension if the driver uses a controller object (see Section 3.4) or in nonpaged pool allocated by the driver.
The DriverEntry routine of such a driver must call KeInitializeDeviceQueue, passing a pointer to the driver-provided storage for the device queue object.
After the driver is loaded, it can insert an IRP into its device queue by calling KeInsertDeviceQueue, which places the IRP at the tail of the queue, or KeInsertByKeyDeviceQueue, which places the IRP into the queue according to a driver-determined SortKey value, as shown in Figure 3.21.
Each of these support routines returns a Boolean value indicating whether the IRP was inserted into the queue. Each of these calls also sets the state of the device queue object to Busy if the queue is currently empty. However, an IRP passed to a KeInsert..DeviceQueue routine is not inserted into the queue on the state transition from Not-Busy to Busy: that is, each of these support routines returns FALSE when it sets the state of an empty device queue object to Busy.
Consequently, NT driver writers who set up supplemental device queues should follow this implementation guideline:
When an IRP is sent to the driver and a call to KeInsert..DeviceQueue returns FALSE, the caller must pass the IRP on for further processing to another driver routine.
However, the call to KeInsert..DeviceQueue with such an IRP changes the state of the device queue object to Busy, so the next IRP to come in is inserted in the queue unless the driver calls KeRemove..DeviceQueue first.
When the device queue object's state is set to Busy, the driver can dequeue an IRP for further processing or reset the state to Not-Busy by calling one of the following support routines:
·KeRemoveDeviceQueue to remove the IRP at the head of the queue
·KeRemoveByKeyDeviceQueue to remove an IRP chosen according to a driver-determined SortKey value
·KeRemoveEntryDeviceQueue to remove a particular IRP in the queue or to determine whether a particular IRP is in the queue
KeRemoveEntryDeviceQueue returns a Boolean indicating whether the given IRP was in the device queue.
An attempt to remove an entry from a device queue that is empty but whose state is set to Busy causes the state transition to Not-Busy.
A device queue object is protected by a built-in executive spin lock (not shown in Figure 3.21), so a driver that uses a device queue object can insert IRPs into the queue and remove them in a multiprocessor-safe manner from any driver routine running at less than or equal to IRQL DISPATCH_LEVEL. This IRQL restriction precludes calling any Ke..DeviceQueue routine from an NT device driver's ISR and SynchCritSection routines, which run at DIRQL.
For more information about managing IRQLs and using executive spin locks, see Chapter 16. For support-routine-specific IRQL requirements, see the Kernel-Mode Driver Reference.