posix_devctl — device control
[DC] #include <devctl.h>
int posix_devctl(int fildes, int dcmd, void *restrict dev_data_ptr,
size_t nbyte, int *restrict dev_info_ptr);
The posix_devctl() function shall cause the device control command dcmd to be passed to the driver identified by fildes. Associated data shall be passed to and/or from the driver depending on direction information encoded in the dcmd argument or as implied in the dcmd argument by the design and implementation of the driver.
If the dev_data_ptr argument is not a null pointer, it shall be a pointer to a buffer that is provided by the caller and that contains data bytes to be passed to the driver or provides space for receiving data bytes to be passed back from the driver, or both.
If the data bytes are to be passed to the driver, at least nbyte bytes of associated data shall be made available to the driver; if the data bytes are to be passed from the driver, no more than nbyte bytes shall be passed.
The driver may be executing in an address space different from the address space of the calling thread. Therefore, if the data bytes passed to the driver (i.e., the contents of the memory area starting at dev_data_ptr and continuing for nbyte bytes) contain pointers to memory in the address space of the calling thread and the driver uses these pointers to access that memory, the effects are unspecified.
[OB] If dev_data_ptr is not a null pointer and nbyte is zero, the amount of data passed to and/or from the driver is unspecified. This feature is obsolescent and is only provided for compatibility with existing device drivers.
If dev_data_ptr is a null pointer, there shall be no data bytes passed between the caller and the driver other than the data specified in the rest of the arguments to posix_devctl() and in its return value.
The dev_info_ptr argument provides the opportunity to return an integer number containing additional device information, instead of just a success/failure indication. For implementation-provided dcmd values, it is implementation-defined whether each such value causes the int pointed to by dev_info_ptr to be set and, if set, what value it is set to.
For each supported device, the set of valid dcmd commands, the associated data interpretation, and the effects of the command on the device are all defined by the driver for the device identified by fildes, and are therefore implementation-defined for implementation-provided device drivers.
Upon successful completion, posix_devctl() shall return zero; otherwise an error number shall be returned to indicate the error. The value returned in the int value pointed to by dev_info_ptr is driver dependent.
The posix_devctl() function shall fail if:
- [EBADF]
- The fildes argument is not a valid open file descriptor.
The posix_devctl() function may fail if:
- [EINTR]
- The posix_devctl() function was interrupted by a signal.
- [EINVAL]
- The nbyte argument exceeds an implementation-defined maximum or is less than the minimum number of bytes required for this command.
- [EINVAL]
- The dcmd argument is not valid for this device.
- [ENOTTY]
- The fildes argument is not associated with a character special file that accepts control functions.
- [EPERM]
- The requesting process does not have the appropriate privilege to request the device to perform the specified command.
Driver code may detect other errors, but the error numbers returned are driver dependent. See "Recommended Practice for Driver-Detected Errors" in RATIONALE.
If the posix_devctl() function fails, the effect of this failed function on the device is driver dependent. Corresponding data might be transferred, partially transferred, or not transferred at all.
None.
None.
Background
An interface to be included in the POSIX standard should improve source code portability of application programs. In traditional UNIX practice, ioctl() was used to handle special devices. Therefore, a general specification of its arguments cannot be written. Based on this fact, in the past many people claimed that ioctl(), or something close to it, had no place in the POSIX standards.
Against this perception stood the widespread use of ioctl() to interface to all sorts of drivers for a vast variety of hardware used in all areas of general-purpose, realtime, and embedded computing, such as analog-digital converters, counters, and video graphic devices. These devices provide a set of services that cannot be represented or used in terms of read() or write() calls.
The arguments in favor of ioctl() standardization can be summarized as follows:
Even if ioctl() addresses very different hardware, many of these devices either are actually the same, interfaced to different computer systems with different implementations of operating systems, or belong to classes of devices with rather high commonality in their functions, e.g., analog-digital converters or digital-analog converters. Growing standardization of the control and status register (CSR) space of these devices allows exploitation of a growing similarity of control codes and data for these devices. A general mechanism is needed to control these devices.
In all these cases, a standardized interface from the application program to drivers for these devices will improve source code portability.
Even if control codes and device data have to be changed when porting applications from one system to another, the definition of ioctl() largely improves readability of a program handling special devices. Changes are confined to more clearly labeled places.
A driver for a specific device normally cannot be considered portable per se, but an application that uses this driver can be made portable if all interfaces needed are well defined and standardized. Users and integrators of realtime systems often add device drivers for specific devices, and a standard interface simplifies this process. Also, device drivers often follow their special hardware from system to system.
In recognition of these reasons, The Open Group included ioctl() in the The Single UNIX Specification, Version 1, and the interface was later incorporated into POSIX.1 under the XSI STREAMS option (although that option was subsequently removed).
The posix_devctl() interface defined in this standard provides an alternative to the the various ioctl() implementations with a standard interface that captures the extensibility of ioctl(), but avoids several of its deficiencies, which is mentioned in "Relationship to ioctl() and the Perceived Needs for Improvement" below.
Existing Practice
The ioctl() interface is widely used. It has provided the generality mentioned above. Existing practice encodes into the second parameter information about data size and direction in some systems. An example of such an encoding is the use in BSD 4.3 of two bits of the command word as read/write bits. However, ioctl() has definite problems with the way that its sometimes optional third parameter can be interpreted.
This practice is similar to the existing POSIX fcntl() function, in which the third parameter can be optional for the F_GETFD and F_GETFL commands, an int when used with the F_DUPFD, F_SETFD, or F_SETFL commands, or a struct flock when used with the F_GETLK, F_SETLK, or F_SETLKW commands. However, the fcntl() interface defines two distinct and known data types as possible for the third parameter. This is not the case in the ioctl() interface, where any number of device driver specific structures and commands are used.
Relationship to ioctl() and the Perceived Needs for Improvement
A.11 General Terminal Interface briefly mentions some of the perceived deficiencies in existing implementations of the ioctl() function, in the context of those ioctl() commands used to implement terminal control. The standard developers decided that, since the set of such control operations was fairly well defined, suitable encapsulations such as tcsetattr(), tcsendbreak(), and tcdrain() could be standardized. These interfaces, while successfully standardizing portable terminal control operations, are not extensible to arbitrary user-supplied devices.
There are several perceived deficiencies with the ioctl() function that drove the development of the posix_devctl() interface as an alternative:
The major problem with ioctl() is that the third argument (when one is passed) varies in both size and type according to the second (command) argument. It is not unprecedented in POSIX, or standards in general, for a function to accept a generic pointer; consider the ISO C function fread(), or the POSIX functions read() and mmap(). However, in all such instances, the generic pointer is accompanied by a size argument that specifies the size of the pointed-to object. Unlike the Ada language, it is, and has always been, the C programmer's responsibility to ensure that these two arguments form a consistent specification of the passed object. But traditional ioctl() implementations do not allow the user to specify the size of the pointed-to object; that size is instead fixed implicitly by the specified command (passed as another argument). The posix_devctl() interface improves upon ioctl() in that it allows the user to specify the object size, thereby restoring the familiar C paradigm for passing a generic object by pointer/size pair.
A secondary problem with ioctl() is that the third argument is sometimes permitted to be interpreted as an integer (int). The posix_devctl() interface clearly requires the dev_data_ptr argument to be a pointer.
A related problem with ioctl() is that the direction(s) in which data are transferred to or from the pointed-to object is neither specified explicitly as an argument (as with mmap()), nor implied by the ioctl() function (as with read()/write(), fread()/fwrite(), or fgets()/fputs()). Instead, the direction is implied by the command argument. In traditional implementations, only the device driver knows the interpretation of the commands and whether data bytes are to be transferred to or from the pointed-to object. But in networked implementations, generic portions of the operating system may need to know the direction to ensure that data bytes are passed properly between a client and a server, separately from device driver concerns. Two implementation-specific solutions to this problem are to always assume data bytes need to be transferred in both directions, or to encode the implied direction into the command word along with the fixed data size. The posix_devctl() interface already provides the implementation with an explicit size parameter. Since the direction is already known implicitly to both the application and the driver and since workable methods exist for implementations to ascertain that direction if required, this perceived problem is strictly an implementation issue and solvable without further impact on the interface.
Finally, posix_devctl() improves upon ioctl() by adopting the new style of error return, avoiding all the problems errno brings to multi-threaded applications. Because the driver-specific information carried by the non-error return values of ioctl() still potentially needs to be passed to the application, posix_devctl() adds the dev_info_ptr argument to specify where this information should be stored.
Which Differences Between posix_devctl() and ioctl() Are Acceptable?
Any differences between the definitions of posix_devctl() and ioctl() have to be perceived as a clear improvement by the community of potential users. Drivers for normal peripherals are typically written by highly specialized professionals. Drivers for the special devices are very often written by the application developer or by the hardware designer. Any interface definition that can be seen as overly complicated will simply not be accepted.
Nevertheless, a few simple and useful improvements to ioctl() are possible, specifically the improvement of type checking, and justify the definition of a new interface.
The major difference between the two interfaces is the addition of the size of the device data. For enhanced compatibility with existing ioctl() implementations, this size can be specified as zero; in this case the amount of data passed is unspecified. (This allows a macro definition of ioctl() that converts it into a posix_devctl() call.) In any case, the data size argument does not contradict the general goal of being able to implement posix_devctl() using the existing ioctl() interfaces provided in current UNIX systems and other POSIX implementations because the standard allows but does not require checking the size of the device data. Although the third argument of the ioctl() function does not specify a size, it is implicit in the specific combination of control command and driver and, therefore, known to the driver implementation.
The method of indicating error return values differs from traditional ioctl() implementations, but it does not preclude the construction of posix_devctl() as a macro built upon ioctl(), which was one of the original design goals.
Rationale for the dev_info_ptr Argument
The POSIX.26 developers felt that it was important to preserve the current ioctl() functionality of allowing a device driver to return some arbitrary piece of information instead of just a success/failure indication. Such information might be, for example, the number of bytes received, the number of bytes that would not fit into the buffer pointed at by dev_data_ptr, the data type indication, or the device status. Current practice for device drivers and ioctl() usage allows such a device-dependent return value. Thus, the concept of an additional output argument, dev_info_ptr, was born.
Rationale for No direction Argument
The initial specification for posix_devctl() contained an additional argument that specified the direction of data flow, i.e., to the driver and/or from the driver. This argument was later removed for the following reasons:
The argument was redundant. Most (if not all) existing implementations encode the direction data either explicitly or implicitly in the command word.
The argument increased the probability of programming errors, since it must be made to agree with the direction information already encoded or implied in the command word or an error would occur.
The only real use of the argument would be if new drivers were written that supported generic commands such as TRANSFER_CONTROL_DATA, which was modified by the direction argument to indicate in which direction the data should be transferred. This is contrary to current practice that uses command pairs such as GET_CONTROL_DATA and PUT_CONTROL_DATA.
The primary purpose of the direction argument was to allow higher levels of the system to identify the direction of data transfers, particularly in the case of remote devices, without having to understand all the commands of all the devices on the system. Implementations that need to ascertain the direction of data transfer from a command word will define a consistent convention for encoding the direction into each command word, and all device drivers supplied by the user must adhere to this convention.
Thus, the data direction argument was removed.
Rationale for Not Defining the Direction Encoding in the dcmd Argument
The POSIX.26 developers gave consideration to defining the direction encoding in the dcmd argument, but decided against doing so. No particular benefit was seen to a predefined encoding, as long as the encoding was used consistently across the entire implementation and was well known to the implementation.
In addition, although only one encoding (BSD's) employed for ioctl() was known among the members of the small working group, it could not be ruled out that other encodings already existed, and no reason for precluding these encodings was seen.
Finally, system or architectural constraints might make a chosen standard encoding difficult to use on a given implementation.
Thus, this standard does not define a direction encoding. Specifying a standard encoding is actually a small part of a larger and more contentious objective, that of specifying a complete set of interfaces for portable device drivers. If a future POSIX standard specifies such interfaces, the issue of device control direction encoding will necessarily be addressed as part of that specification.
Recommended Practice for Handling Data Size Errors
In the event that the amount of data from the device is too large to fit into the specified buffer, as much data as will fit should be transferred, and the error posted. The retained data will aid in debugging, even if some of the data is lost.
Recommended Practice for nbyte == 0
The feature that permits an unspecified amount of control data to be transferred if nbyte is zero exists only for compatibility with existing device driver usage of ioctl(), i.e., when ioctl() is implemented on top of posix_devctl() and the device driver transfers an amount of data implied by the command.
Implementations in which posix_devctl() is built as a library routine on top of ioctl() may not be able to make checks on the nbyte argument. However, newly developed applications using posix_devctl() should always use an appropriate value for the nbyte argument, for portability to implementations directly supporting posix_devctl() in which the device drivers may be able to honor the application's nbyte argument or return the error [EINVAL] if the argument is an unacceptable value. Device drivers designed for those systems should interpret a zero value of nbyte as no data to be transferred.
Recommended Practice for Driver-Detected Errors
If the driver detects the following error conditions, it is recommended that the posix_devctl() function fail and return the corresponding error number:
- [EBUSY]
- The control operation could not complete successfully because the device was in use by another process, or the driver was unable to carry out the request due to an outstanding operation in progress.
- [EINVAL]
- The arguments dev_data_ptr and nbyte define a buffer too small to hold the amount of data expected by or to be returned by this driver.
- [EIO]
- The control operation could not complete successfully because the driver detected a hardware error.
None.
First released in Issue 8. Derived from POSIX.26.
return to top of page