Expand description
Provide helpers for making ioctl system calls.
This library is pretty low-level and messy. ioctl
is not fun.
What is an ioctl
?
The ioctl
syscall is the grab-bag syscall on POSIX systems. Don’t want to add a new
syscall? Make it an ioctl
! ioctl
refers to both the syscall, and the commands that can be
sent with it. ioctl
stands for “IO control”, and the commands are always sent to a file
descriptor.
It is common to see ioctl
s used for the following purposes:
- Provide read/write access to out-of-band data related to a device such as configuration (for instance, setting serial port options)
- Provide a mechanism for performing full-duplex data transfers (for instance, xfer on SPI devices).
- Provide access to control functions on a device (for example, on Linux you can send commands like pause, resume, and eject to the CDROM device.
- Do whatever else the device driver creator thought made most sense.
ioctl
s are synchronous system calls and are similar to read and write calls in that regard.
They operate on file descriptors and have an identifier that specifies what the ioctl is.
Additionally they may read or write data and therefore need to pass along a data pointer.
Besides the semantics of the ioctls being confusing, the generation of this identifer can also
be difficult.
Historically ioctl
numbers were arbitrary hard-coded values. In Linux (before 2.6) and some
unices this has changed to a more-ordered system where the ioctl numbers are partitioned into
subcomponents (For linux this is documented in
Documentation/ioctl/ioctl-number.rst
):
- Number: The actual ioctl ID
- Type: A grouping of ioctls for a common purpose or driver
- Size: The size in bytes of the data that will be transferred
- Direction: Whether there is any data and if it’s read, write, or both
Newer drivers should not generate complete integer identifiers for their ioctl
s instead
preferring to use the 4 components above to generate the final ioctl identifier. Because of
how old ioctl
s are, however, there are many hard-coded ioctl
identifiers. These are
commonly referred to as “bad” in ioctl
documentation.
Defining ioctl
s
This library provides several ioctl_*!
macros for binding ioctl
s. These generate public
unsafe functions that can then be used for calling the ioctl. This macro has a few different
ways it can be used depending on the specific ioctl you’re working with.
A simple ioctl
is SPI_IOC_RD_MODE
. This ioctl works with the SPI interface on Linux. This
specific ioctl
reads the mode of the SPI device as a u8
. It’s declared in
/include/uapi/linux/spi/spidev.h
as _IOR(SPI_IOC_MAGIC, 1, __u8)
. Since it uses the _IOR
macro, we know it’s a read
ioctl and can use the ioctl_read!
macro as follows:
const SPI_IOC_MAGIC: u8 = b'k'; // Defined in linux/spi/spidev.h
const SPI_IOC_TYPE_MODE: u8 = 1;
ioctl_read!(spi_read_mode, SPI_IOC_MAGIC, SPI_IOC_TYPE_MODE, u8);
This generates the function:
pub unsafe fn spi_read_mode(fd: c_int, data: *mut u8) -> Result<c_int> {
let res = libc::ioctl(fd, request_code_read!(SPI_IOC_MAGIC, SPI_IOC_TYPE_MODE, mem::size_of::<u8>()), data);
Errno::result(res)
}
The return value for the wrapper functions generated by the ioctl_*!
macros are nix::Error
s.
These are generated by assuming the return value of the ioctl is -1
on error and everything
else is a valid return value. If this is not the case, Result::map
can be used to map some
of the range of “good” values (-Inf..-2, 0..Inf) into a smaller range in a helper function.
Writing ioctl
s generally use pointers as their data source and these should use the
ioctl_write_ptr!
. But in some cases an int
is passed directly. For these ioctl
s use the
ioctl_write_int!
macro. This variant does not take a type as the last argument:
const HCI_IOC_MAGIC: u8 = b'k';
const HCI_IOC_HCIDEVUP: u8 = 1;
ioctl_write_int!(hci_dev_up, HCI_IOC_MAGIC, HCI_IOC_HCIDEVUP);
Some ioctl
s don’t transfer any data, and those should use ioctl_none!
. This macro
doesn’t take a type and so it is declared similar to the write_int
variant shown above.
The mode for a given ioctl
should be clear from the documentation if it has good
documentation. Otherwise it will be clear based on the macro used to generate the ioctl
number where _IO
, _IOR
, _IOW
, and _IOWR
map to “none”, “read”, “write_*”, and “readwrite”
respectively. To determine the specific write_
variant to use you’ll need to find
what the argument type is supposed to be. If it’s an int
, then write_int
should be used,
otherwise it should be a pointer and write_ptr
should be used. On Linux the
ioctl_list
man page describes a
large number of ioctl
s and describes their argument data type.
Using “bad” ioctl
s
As mentioned earlier, there are many old ioctl
s that do not use the newer method of
generating ioctl
numbers and instead use hardcoded values. These can be used with the
ioctl_*_bad!
macros. This naming comes from the Linux kernel which refers to these
ioctl
s as “bad”. These are a different variant as they bypass calling the macro that generates
the ioctl number and instead use the defined value directly.
For example the TCGETS
ioctl
reads a termios
data structure for a given file descriptor.
It’s defined as 0x5401
in ioctls.h
on Linux and can be implemented as:
ioctl_read_bad!(tcgets, TCGETS, termios);
The generated function has the same form as that generated by ioctl_read!
:
pub unsafe fn tcgets(fd: c_int, data: *mut termios) -> Result<c_int>;
Working with Arrays
Some ioctl
s work with entire arrays of elements. These are supported by the ioctl_*_buf
family of macros: ioctl_read_buf
, ioctl_write_buf
, and ioctl_readwrite_buf
. Note that
there are no “bad” versions for working with buffers. The generated functions include a len
argument to specify the number of elements (where the type of each element is specified in the
macro).
Again looking to the SPI ioctl
s on Linux for an example, there is a SPI_IOC_MESSAGE
ioctl
that queues up multiple SPI messages by writing an entire array of spi_ioc_transfer
structs.
linux/spi/spidev.h
defines a macro to calculate the ioctl
number like:
#define SPI_IOC_MAGIC 'k'
#define SPI_MSGSIZE(N) ...
#define SPI_IOC_MESSAGE(N) _IOW(SPI_IOC_MAGIC, 0, char[SPI_MSGSIZE(N)])
The SPI_MSGSIZE(N)
calculation is already handled by the ioctl_*!
macros, so all that’s
needed to define this ioctl
is:
const SPI_IOC_MAGIC: u8 = b'k'; // Defined in linux/spi/spidev.h
const SPI_IOC_TYPE_MESSAGE: u8 = 0;
ioctl_write_buf!(spi_transfer, SPI_IOC_MAGIC, SPI_IOC_TYPE_MESSAGE, spi_ioc_transfer);
This generates a function like:
pub unsafe fn spi_message(fd: c_int, data: &mut [spi_ioc_transfer]) -> Result<c_int> {
let res = libc::ioctl(fd,
request_code_write!(SPI_IOC_MAGIC, SPI_IOC_TYPE_MESSAGE, data.len() * mem::size_of::<spi_ioc_transfer>()),
data);
Errno::result(res)
}
Finding ioctl
Documentation
For Linux, look at your system’s headers. For example, /usr/include/linux/input.h
has a lot
of lines defining macros which use _IO
, _IOR
, _IOW
, _IOC
, and _IOWR
. Some ioctl
s are
documented directly in the headers defining their constants, but others have more extensive
documentation in man pages (like termios’ ioctl
s which are in tty_ioctl(4)
).
Documenting the Generated Functions
In many cases, users will wish for the functions generated by the ioctl
macro to be public and documented. For this reason, the generated functions
are public by default. If you wish to hide the ioctl, you will need to put
them in a private module.
For documentation, it is possible to use doc comments inside the ioctl_*!
macros. Here is an
example :
ioctl_read! {
/// Make the given terminal the controlling terminal of the calling process. The calling
/// process must be a session leader and not have a controlling terminal already. If the
/// terminal is already the controlling terminal of a different session group then the
/// ioctl will fail with **EPERM**, unless the caller is root (more precisely: has the
/// **CAP_SYS_ADMIN** capability) and arg equals 1, in which case the terminal is stolen
/// and all processes that had it as controlling terminal lose it.
tiocsctty, b't', 19, c_int
}
Macros
- Generate an ioctl request code for a command that passes no data.
- Generate an ioctl request code for a command that reads.
- Generate an ioctl request code for a command that reads and writes.
- Generate an ioctl request code for a command that writes.