Creating IOCTL Requests in Drivers
A class driver or other higher-level driver can allocate IRPs for I/O control requests and send them to the next-lower driver as follows:
Allocate or reuse an I/O request packet (IRP) with the major function code IRP_MJ_DEVICE_CONTROL or IRP_MJ_INTERNAL_DEVICE_CONTROL. You can use the IoBuildDeviceIoControlRequest routine to specifically allocate an IOCTL IRP. You can also use general-purpose IRP creation and initialization routines, such as IoAllocateIrp, IoReuseIrp, or IoInitializeIrp. For more information about IRP allocation, see Creating IRPs for Lower-Level Drivers.
Set up the lower driver's I/O stack location for the IRP with the IOCTL_XXX code and appropriate parameters.
If the IOCTL request is to be completed asynchronously, call the KeInitializeEvent routine to initialize an event object as a notification event. The driver uses this event to wait for an I/O operation to be completed.
Determine how the lower driver handled a given request.
Reuse the IRP to send another request or dispose of the driver-created IRP, after the lower driver completes a requested operation. The driver cannot reuse IRPs that IoBuildDeviceIoControlRequest created. For more information, see Reusing IRPs.
Call IoCallDriver to pass the request on to the lower driver.
If IoCallDriver returns STATUS_PENDING, call the KeWaitForSingleObject routine to put the current thread into a wait state. The driver sets the routine's Object parameter to the address of the event object that was initialized in the call to KeInitializeEvent.
Note If the driver calls KeWaitForSingleObject with its Timeout parameter set either to NULL or to the address of a variable that contains a nonzero value, the driver must be running at IRQL <= APC_LEVEL in a nonarbitrary thread context. Otherwise, the driver must be running at IRQL <= DISPATCH_LEVEL.
The event is signaled by its IoCompletion routine when the IOCTL request has completed. Once the event is signaled, the thread resumes execution.
Important If the driver allocates the event object as a local variable on the stack, the driver must call KeWaitForSingleObject with its WaitMode parameter set to KernelMode. This parameter value prevents the stack from being paged out.
To avoid synchronization problems and possible access violations, parameters for I/O control codes rarely include embedded pointers. Except for certain SCSI requests, the buffers at Irp->AssociatedIrp.SystemBuffer, at Irp->MdlAddress, and at Parameters.DeviceIoControl.Type3InputBuffer in a driver's I/O stack location do not contain pointers to other data buffers, nor do they contain structures that contain pointers for system-defined I/O control codes. For more information about how data buffers are used with IRPs that contain I/O control codes, see Buffer Descriptions for I/O Control Codes.
Nevertheless, a pair of class/port drivers that define internal I/O control codes can pass an embedded pointer to driver-allocated memory from the higher-level driver to the lower-level driver. Such a pair of class/port drivers is responsible for ensuring that the following is true:
Only one driver at a time can access the data.
Private data buffers are accessible in an arbitrary thread context by the port driver.
Display drivers can call the GDI function EngDeviceIoControl to send privately defined, device-specific I/O control requests, as well as system-defined public I/O control requests, through the system video port driver down to the corresponding adapter-specific video miniport drivers.
Any user-mode component of a driver package can call DeviceIoControl to send I/O control requests to a driver stack. The I/O manager creates an IRP_MJ_DEVICE_CONTROL request and delivers it to the highest-level driver.