So when I call an ioctl on a device, with an ioctl number, how does it know which function to call?
In computing, ioctl (an abbreviation of input/output control) is a system call for device-specific input/output operations and other operations which cannot be expressed by regular system calls. It takes a parameter specifying a request code; the effect of a call depends completely on the request code.
The ioctl function is useful for implementing a device driver to set the configuration on the device. e.g. a printer that has configuration options to check and set the font family, font size etc. ioctl could be used to get the current font as well as set the font to a new one.
The ioctl() system call manipulates the underlying device parameters of special files. In particular, many operating characteristics of character special files (e.g., terminals) may be controlled with ioctl() requests.
To invoke ioctl commands of a device, the user-space program would open the device first, then send the appropriate ioctl() and any necessary arguments. static int mydrvr_ioctl (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
The ioctl(2)
enters via the fs/ioctl.c
function:
SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
{
struct file *filp;
int error = -EBADF;
int fput_needed;
filp = fget_light(fd, &fput_needed);
if (!filp)
goto out;
error = security_file_ioctl(filp, cmd, arg);
if (error)
goto out_fput;
error = do_vfs_ioctl(filp, fd, cmd, arg);
out_fput:
fput_light(filp, fput_needed);
out:
return error;
}
Note that there is already a filedescriptor fd
associated. The kernel then calls fget_light()
to look up a filp
(roughly, file pointer, but don't confuse this with the standard IO FILE *
file pointer). The call into security_file_ioctl()
checks whether the loaded security module will allow the ioctl
(whether by name, as in AppArmor and TOMOYO, or by labels, as in SMACK and SELinux), as well as whether or not the user has the correct capability (capabilities(7)) to make the call. If the call is allowed, then do_vfs_ioctl()
is called to either handle common ioctls itself:
switch (cmd) {
case FIOCLEX:
set_close_on_exec(fd, 1);
break;
/* ... */
If none of those common cases are correct, then the kernel calls a helper routine:
static long vfs_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
int error = -ENOTTY;
if (!filp->f_op || !filp->f_op->unlocked_ioctl)
goto out;
error = filp->f_op->unlocked_ioctl(filp, cmd, arg);
if (error == -ENOIOCTLCMD)
error = -EINVAL;
out:
return error;
}
Drivers supply their own .unlocked_ioctl
function pointer, like this pipe implementation in fs/pipe.c
:
const struct file_operations rdwr_pipefifo_fops = {
.llseek = no_llseek,
.read = do_sync_read,
.aio_read = pipe_read,
.write = do_sync_write,
.aio_write = pipe_write,
.poll = pipe_poll,
.unlocked_ioctl = pipe_ioctl,
.open = pipe_rdwr_open,
.release = pipe_rdwr_release,
.fasync = pipe_rdwr_fasync,
};
There's a map in the kernel. You can register your own ioctl codes if you write a driver.
Edit: I wrote an ATA over Ethernet driver once and implemented a custom ioctl for tuning the driver at runtime.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With