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.1 I/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:

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.