1.3 Defining I/O Control Codes
All system-defined I/O control codes for IRP_MJ_DEVICE_CONTROL requests can be considered public in the sense that they are exported to one or more user-mode protected subsystems that run on top of the Windows NT executive. As public device I/O control codes, some are also assumed to be exported to user-mode applications native to a protected subsystem, particularly to Win32 applications.
For a new kind of device, the driver’s designer can define a public set of I/O control codes for IRP_MJ_DEVICE_CONTROL requests. However, since such a set of codes should be generally useful to other drivers of similar devices in the future, public I/O control codes must have the approval of and must be built into the system by Microsoft Corporation.
For new devices or for common kinds of devices with special features, driver designers also can define a set of I/O control codes for IRP_MJ_INTERNAL_DEVICE_CONTROL requests. Such a set of internal I/O control codes can be used by paired kernel-mode drivers to control the underlying device.
For example, kernel-mode drivers designed to the class/port model might use such a set of internal I/O control codes to take advantage of the special features of a particular device or type of device. The system-defined SCSI class/port interface uses this technique to define a SCSI-specific set of requests that class drivers send down to the system SCSI port driver, which transforms them into OS-independent SCSI requests for HBA-specific miniport drivers.
When it is sent a device control request, a class driver sets up the next-lower port driver’s I/O stack location in the IRP and passes the request on to the underlying device driver, like any other higher-level driver.
A class driver also can allocate IRPs for I/O control requests and send them to the underlying port driver as follows:
1.Call IoBuildDeviceIoControlRequest to allocate an IRP with the major function code IRP_MJ_DEVICE_CONTROL or IRP_MJ_INTERNAL_DEVICE_CONTROL.
2.Set up the port driver’s I/O stack location in the IRP with the IOCTL_XXX and appropriate parameters.
3.Note that parameters for a system-defined I/O control code almost never include an embedded pointer to avoid synchronization problems and possible access violations. With the exception of certain SCSI requests, the buffers at Irp->AssociatedIrp.SystemBuffer, at Irp->MdlAddress, and/or at Parameters.DeviceIoControl.Type3InputBuffer in the I/O stack location of the IRP neither have a pointer to another data buffer nor contain a structure with such a pointer for the public and system-defined internal I/O control codes.
4.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 device driver. Such a pair of class/port drivers is responsible for ensuring that only one driver at a time can access the data and that their private data buffer is accessible in an arbitrary thread context by the port driver.
5.Call IoSetCompletionRoutine with the IRP, as necessary, so that the class driver can determine how the corresponding port driver handled a given request, reuse the IRP to send another request, or dispose of a driver-created IRP when the port driver completes a requested operation.
6.Call IoCallDriver to pass the request on to the port driver.
By calling the GDI function EngDeviceIoControl, a display driver also can 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 driver.
With a call to DeviceIoControl, a user-mode VDD can send I/O control requests to the corresponding kernel-mode driver for an MS-DOS-application-dedicated device.
For more information about the functionality of video miniport drivers and display drivers, see the Graphics Driver Design Guide. For additional VDD details see Virtual DOS Drivers.
Figure 1.1 illustrates the layout of I/O control codes.
Figure 1.1I/O Control Code Layout
Designers of drivers for a new FILE_DEVICE_XXX type of device must set the Common flag at bit 31 in the private I/O control codes they define. Those who define a private set of I/O control codes for IRP_MJ_DEVICE_CONTROL or IRP_MJ_INTERNAL_DEVICE_CONTROL requests also must set the Custom flag at bit 13 in the I/O control codes they define.
All system-defined I/O control codes have both these C flags cleared.
Driver writers can use the system-supplied macro CTL_CODE to set up new I/O control codes. To define an I/O control code, follow these guidelines for using CTL_CODE:
·Choose a descriptive constant name of the form IOCTL_Device_Function, where Device indicates the type of device and Function indicates the operation.
·Supply the following parameters to the CTL_CODE macro in the following order:
1.DeviceType matches that set in the Type field of the driver’s device objects (see Section 1.1).
2.FunctionCode is in the range 0x800 to 0xfff for private I/O control codes defined by customers of Microsoft. Values in the range 0x000 to 0x7ff are reserved by Microsoft for public I/O control codes. The CTL_CODE macro automatically sets the Custom flag at bit 13 for values in the range 0x800 to 0xfff.
3.TransferType indicates how data is passed to the driver as one of the following system-defined constants:
METHOD_BUFFERED if the driver transfers small amounts of data for the request
With this method, IRPs containing the I/O control code will supply a pointer to the buffer into which or from which to transfer data at
Irp->AssociatedIrp.SystemBuffer. Most I/O control codes for device and intermediate drivers use this TransferType value.
METHOD_IN_DIRECT if the underlying device driver will read a large amount of data for the request using DMA or PIO and must transfer the data quickly
With this method, IRPs containing the I/O control code will supply a pointer to an MDL, describing the output buffer at Irp->MdlAddress.
METHOD_OUT_DIRECT if the underlying device driver will write a large amount of data to the device for the request using DMA or PIO and must transfer the data quickly
With this method, IRPs containing the I/O control code will supply a pointer to an MDL, describing the data buffer, at Irp->MdlAddress.
METHOD_NEITHER if the driver can be sent such a request only while it is running in the context of the thread that originates the I/O control request
Only a highest-level kernel-mode driver is guaranteed to meet this condition, so this value is seldom used for the I/O control codes passed to device drivers. With this method, the highest-level driver must determine whether to set up buffered or direct access to user data on receipt of the request, possibly must lock down the user buffer, and must wrap its access to the user buffer in a structured exception handler. Otherwise, the originating user-mode caller might change the buffered data out from under the driver or the caller could be swapped out just as the driver is accessing the user buffer.
4.RequiredAccess indicates the type of access that must be requested when the caller opens the file object representing the device (see the IRP_MJ_CREATE request in Section 1.2). In other words, the I/O Manager will create IRPs and call the driver with a given I/O control code only if the caller has requested the necessary access rights for the driver to perform the requested operation. RequiredAccess can be one of the following system-defined constants:
FILE_ANY_ACCESS if the driver can carry out the requested operation for any caller that has a handle for the file object representing the target device object
FILE_READ_DATA if the driver can carry out the requested operation only for a caller with read access rights
With this required access, the underlying device driver transfers data from the device to system memory.
FILE_WRITE_DATA if the driver can carry out the requested operation only for a caller with write access rights
With this required access, the underlying device driver transfers data from system memory to its device.
(FILE_READ_DATA | FILE_WRITE_DATA) if the caller must have both read and write access rights
With this required access, the underlying device driver transfers data between system memory and the device
Most public I/O control requests sent to device drivers are assigned FILE_ANY_ACCESS as their RequiredAccess value, particularly those sent to drivers of exclusive devices and those that are buffered by the I/O Manager or a higher-level driver. Many internal I/O control requests for system-supplied drivers also specify this type of RequiredAccess.
However, for certain types of devices, the public I/O control codes require the caller to have read access rights, write access rights, or both.
For example, the definition of the public I/O control code IOCTL_DISK_SET_PARTITION_INFO shows that this I/O request can be sent to a disk driver and to all drivers layered above the disk driver only if the caller has both read and write access rights, as shown by the following definition:
#define IOCTL_DISK_SET_PARTITION_INFO\
CTL_CODE(IOCTL_DISK_BASE, 0x008, METHOD_BUFFERED,\
FILE_READ_DATA | FILE_WRITE_DATA)
Driver designers who want to define a set of public control codes must consult with Microsoft Corporation to have new codes added to the system header files. Private I/O control codes should be defined in the driver(s)’ device-specific header files.