The DeviceIoControl API

The DeviceIoControl API

The user-mode DeviceIoControl API has the following prototype:

result = DeviceIoControl(Handle, Code, InputData, InputLength, OutputData, OutputLength, &Feedback, &Overlapped);

Handle (HANDLE) is an open handle open to the device. You obtain this handle by calling CreateFile in the following manner:

Handle = CreateFile("\\\\.\\IOCTL", GENERIC_READ GENERIC_WRITE, 0, NULL, OPEN_EXISTING, flags, NULL); if (Handle == INVALID_HANDLE_VALUE) <error> CloseHandle(Handle);

The flags argument to CreateFile is either FILE_FLAG_OVERLAPPED or 0 to indicate whether you ll be performing asynchronous operations with this file handle. While you have the handle open, you can make calls to ReadFile, WriteFile, or DeviceIoControl. When you re done accessing the device, you should explicitly close the handle by calling CloseHandle. Bear in mind, though, that the operating system automatically closes any handles that are left open when your process terminates.

The Code (DWORD) argument to DeviceIoControl is a control code that indicates the control operation you want to perform. I ll discuss how you define these codes a bit further on (in Defining I/O Control Codes ). The InputData (PVOID) and InputLength (DWORD) arguments describe a data area that you re sending to the device driver. (That is, this data is input from the perspective of the driver.) The OutputData (PVOID) and OutputLength (DWORD) arguments describe a data area that the driver can completely or partially fill with information that it wants to send back to you. (That is, this data is output from the perspective of the driver.) The driver will update the Feedback variable (a DWORD) to indicate how many bytes of output data it gave you back. Figure 9-1 illustrates the relationship of these buffers with the application and the driver. The Overlapped (OVERLAPPED) structure is used to help control an asynchronous operation, which is the subject of the next section. If you specified FILE_FLAG_OVERLAPPED in the call to CreateFile, you must specify the OVERLAPPED structure pointer. If you didn t specify FILE_FLAG_OVERLAPPED, you might as well supply NULL for this last argument because the system is going to ignore it anyway.

figure 9-1 input and output buffers for deviceiocontrol.

Figure 9-1. Input and output buffers for DeviceIoControl.

Whether a particular control operation requires an input buffer or an output buffer depends on the function being performed. For example, an I/O control (IOCTL) that retrieves the driver s version number probably requires an output buffer only. An IOCTL that merely notifies the driver of some fact pertaining to the application probably requires only an input buffer. You can imagine still other operations that require either both or neither of the input and output buffers it all depends on what the control operation does.

The return value from DeviceIoControl is a Boolean value that indicates success (if TRUE) or failure (if FALSE). In a failure situation, the application can call GetLastError to find out why the call failed.

Synchronous and Asynchronous Calls to DeviceIoControl

When you make a synchronous call to DeviceIoControl, the calling thread blocks until the control operation completes. For example:

HANDLE Handle = CreateFile("\\\\.\\IOCTL", ..., 0, NULL); DWORD version, junk; if (DeviceIoControl(Handle, IOCTL_GET_VERSION_BUFFERED, NULL, 0, &version, sizeof(version), &junk, NULL)) printf("IOCTL.SYS version %d.%2.2d\n", HIWORD(version), LOWORD(version)); else printf("Error %d in IOCTL_GET_VERSION_BUFFERED call\n", GetLastError());

Here we open the device handle without the FILE_FLAG_OVERLAPPED flag. Our subsequent call to DeviceIoControl therefore doesn t return until the driver supplies the answer we re asking for.

When you make an asynchronous call to DeviceIoControl, the calling thread doesn t block immediately. Instead, it continues processing until it reaches the point where it requires the result of the control operation. At that point, it calls an API that will block the thread until the driver completes the operation. For example:

HANDLE Handle = CreateFile("\\\\.\\IOCTL", ..., FILE_FLAG_OVERLAPPED, NULL); DWORD version, junk; OVERLAPPED Overlapped; Overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); DWORD code; if (DeviceIoControl(Handle, ..., &Overlapped)) code = 0; else code = GetLastError(); <continue processing> if (code == ERROR_IO_PENDING) { if (GetOverlappedResult(Handle, &Overlapped, &junk, TRUE)) code = 0; else code = GetLastError(); } CloseHandle(Overlapped.hEvent); if (code != 0) <error>

Two major differences exist between this asynchronous example and the earlier synchronous example. First, we specify the FILE_FLAG_OVERLAPPED flag in the call to CreateFile. Second, the call to DeviceIoControl specifies the address of an OVERLAPPED structure, within which we ve initialized the hEvent event handle to describe a manual reset event. (For more information about events and thread synchronization in general, see Jeffrey Richter s Programming Applications for Microsoft Windows, Fourth Edition [Microsoft Press, 1999].)

The asynchronous call to DeviceIoControl will have one of three results. First, it might return TRUE, meaning that the device driver s dispatch routine was able to complete the request right away. Second, it might return FALSE, and GetLastError might retrieve the special error code ERROR_IO_PENDING. This result indicates that the driver s dispatch routine returned STATUS_PENDING and will complete the control operation later. Note that ERROR_IO_PENDING isn t really an error it s one of the two ways in which the system indicates that everything is proceeding normally. The third possible result from the asynchronous call to DeviceIoControl is a FALSE return value coupled with a GetLast Error value other than ERROR_IO_PENDING. Such a result would be a real error.

At the point at which the application needs the result of the control operation, it calls one of the Win32 synchronization primitives GetOverlapped Result, WaitForSingleObject, or the like. GetOverlappedResult, the synchronization primitive I use in this example, is especially convenient because it also retrieves the bytes-transferred feedback value and sets the GetLastError result to indicate the result of the I/O operation. Although you can call WaitForSingleObject or a related API passing the Overlapped.hEvent event handle as an argument you won t be able to learn the results of the DeviceIoControl operation; you ll just learn that the operation has finished.

Defining I/O Control Codes

The Code argument to DeviceIoControl is a 32-bit numeric constant that you define using the CTL_CODE preprocessor macro that s part of both the DDK and the Platform SDK. Figure 9-2 illustrates the way in which the operating system partitions one of these 32-bit codes into subfields.

figure 9-2 fields in an i/o control code.

Figure 9-2. Fields in an I/O control code.

The fields have the following interpretation:

  • The device type (16 bits, first argument to CTL_CODE) indicates the type of device that implements this control operation. You should use the same value (for example, FILE_DEVICE_UNKNOWN) that you use in the driver when you call IoCreateDevice. (File system device type codes cause the I/O Manager to use a different major function code for the IRP it sends you.)

  • The access code (2 bits, fourth argument to CTL_CODE) indicates the access rights an application needs to its device handle to issue this control operation.

  • The function code (12 bits, second argument to CTL_CODE) indicates precisely which control operation this code describes. Microsoft reserves the first half of the range of this field that is, values 0 through 2047 for standard control operations. You and I therefore assign values in the range 2048 through 4095. The main purpose of this convention is to allow you to define private control operations for standard devices.

  • The buffering method (2 bits, third argument to CTL_CODE) indicates how the I/O Manager is to handle the input and output buffers supplied by the application. I ll have a great deal to say about this field in the next section, when I describe how to implement IRP_MJ_DEVICE_CONTROL in a driver.

I want to clarify one point of possible confusion. When you create your driver, you re free to design a series of IOCTL operations that applications can use in talking to your driver. Although some other driver author might craft a set of IOCTL operations that uses exactly the same numeric values for control codes, the system will never be confused by the overlap because IOCTL codes are interpreted by only the driver to which they re addressed. Mind you, if you opened a handle to a device belonging to that hypothetical other driver and then tried to send what you thought was one of your own IOCTLs to it, confusion would definitely ensue.

The access code in an I/O control code gives you the ability to divide the world of users into four parts, based on a security descriptor you attach to your device object:

  • Users to whom the security descriptor denies all access can t open a handle, so they can t issue any IOCTLs at all.

  • Users whom the security descriptor allows to open handles for reading but not writing can issue IOCTLs whose function code specifies FILE_READ_ACCESS or FILE_ANY_ACCESS, but not those whose code specifies FILE_WRITE_ACCESS.

  • Users whom the security descriptor allows to open handles for writing but not reading can issue IOCTLs whose function code specifies FILE_WRITE_ACCESS or FILE_ANY_ACCESS, but not those whose code specifies FILE_READ_ACCESS.

  • Users whom the security descriptor allows to open handles for both reading and writing can issue any IOCTL.

Mechanically, your life and the life of application programmers who need to call your driver will be easier if you place all of your IOCTL definitions in a dedicated header file. In the samples in the companion content, the projects each have a header named IOCTLS.H that contains these definitions. For example:

#ifndef CTL_CODE #pragma message ( \ "CTL_CODE undefined. Include winioctl.h or wdm.h") #endif #define IOCTL_GET_VERSION_BUFFERED \ CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, \ FILE_ANY_ACCESS) #define IOCTL_GET_VERSION_DIRECT \ CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_OUT_DIRECT, \ FILE_ANY_ACCESS) #define IOCTL_GET_VERSION_NEITHER \ CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_NEITHER, \ FILE_ANY_ACCESS)

The reason for the message #pragma, by the way, is that I m forever forgetting to include the header file (WINIOCTL.H) that defines CTL_CODE for user-mode programs, and I also tend to forget the name. Better a message that will tell me what I m doing wrong than a few minutes grep ing through the include directory, I always say.



Programming the Microsoft Windows Driver Model
Programming the Microsoft Windows Driver Model
ISBN: 0735618038
EAN: 2147483647
Year: 2003
Pages: 119
Authors: Walter Oney

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net