Ioctl Linux Device Driver

IOCTL Linux device driver

An ioctl, which means "input-output control" is a kind of device-specific system call. There are only a few system calls in Linux (300-400), which are not enough to express all the unique functions devices may have. So a driver can define an ioctl which allows a userspace application to send it orders. However, ioctls are not very flexible and tend to get a bit cluttered (dozens of "magic numbers" which just work... or not), and can also be insecure, as you pass a buffer into the kernel - bad handling can break things easily.

An alternative is the sysfs interface, where you set up a file under /sys/ and read/write that to get information from and to the driver. An example of how to set this up:

static ssize_t mydrvr_version_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%s\n", DRIVER_RELEASE);
}

static DEVICE_ATTR(version, S_IRUGO, mydrvr_version_show, NULL);

And during driver setup:

device_create_file(dev, &dev_attr_version);

You would then have a file for your device in /sys/, for example, /sys/block/myblk/version for a block driver.

Another method for heavier use is netlink, which is an IPC (inter-process communication) method to talk to your driver over a BSD socket interface. This is used, for example, by the WiFi drivers. You then communicate with it from userspace using the libnl or libnl3 libraries.

Which driver is handling my IOCTL


The fish

You are calling ioctl on a filedescriptor that is representing socket. If you examine net/socket.c file, you will find socket_file_ops structure that defines sock_ioctl as a ioctl callback. For SIOCETHTOOL, this function will call sock_do_ioctl which in turn (after checking that this particular socket type (AF_INET - af_inet.c, inet_ioctl function) does not handle this ioctl by itself) will call dev_ioctl which can handle SIOCETHTOOL by calling dev_ethtool. If your driver defines ethtool_ops, this should be supported.

Now finding which device driver supports your network interface is another thing but this isn't really hard. One way to do this is to use sysfs, just check what is this symlink pointing to (substituting eth0 with your interface name):

readlink /sys/class/net/eth0/device/driver/module
../../../../module/e1000e

So my ethernet card is driven by module e1000e.

The fishing rod

Now I found this out by reading the code. I do know something about kernel code so I knew where I should look. If I wouldn't, however, there's other way to find this out - tracing. Now I'm not sure if all this works on 2.6.32, but at least I tested this on kernel 3.2 (which isn't that much different). I won't go into details on how to configure your kernel to have all those functionalities. Ubuntu 12.04 has all that is needed and if you're interested google for ftrace:

You need debugfs mounted and a script like this:

#!/bin/sh
DEBUGFS=`/sys/kernel/debug/`
echo $$ > $DEBUGFS/tracing/set_ftrace_pid
echo function > $DEBUGFS/tracing/current_tracer
exec $*

This script will set ftrace to only care about current PID, enable function tracer and exec the command specified as an argument (without forking as that would change the PID).

Now, if your application that is using this ioctl is /tmp/a.out, you can call:

~/bin/ftraceme.sh /tmp/a.out
echo -n "" > /sys/kernel/debug/tracing/current_tracer
grep ioctl /sys/kernel/debug/tracing/trace

In my case, I've got:

<...>-11009 [007] 596251.750675: sys_ioctl <-system_call_fastpath    (1)
<...>-11009 [007] 596251.750675: fget_light <-sys_ioctl
<...>-11009 [007] 596251.750675: security_file_ioctl <-sys_ioctl
<...>-11009 [007] 596251.750676: cap_file_ioctl <-security_file_ioctl
<...>-11009 [007] 596251.750676: do_vfs_ioctl <-sys_ioctl
<...>-11009 [007] 596251.750676: sock_ioctl <-do_vfs_ioctl (2)
<...>-11009 [007] 596251.750677: sock_do_ioctl <-sock_ioctl (3)
<...>-11009 [007] 596251.750677: inet_ioctl <-sock_do_ioctl (4)
<...>-11009 [007] 596251.750677: udp_ioctl <-inet_ioctl
<...>-11009 [007] 596251.750678: dev_ioctl <-sock_do_ioctl (5)
<...>-11009 [007] 596251.750678: _cond_resched <-dev_ioctl
<...>-11009 [007] 596251.750679: dev_load <-dev_ioctl
<...>-11009 [007] 596251.750680: rtnl_lock <-dev_ioctl
<...>-11009 [007] 596251.750680: dev_ethtool <-dev_ioctl (6)
<...>-11009 [007] 596251.750684: rtnl_unlock <-dev_ioctl
<...>-11009 [007] 596251.750685: _cond_resched <-dev_ioctl

Which proves what I wrote above.

Block device driver - Understanding received ioctl

Setting up a loop device (sudo losetup loop0 /path/to/some/image), the output of strace dd if=/dev/loop0 of=out.data skip=10 bs=40 count=2 contains

open("/dev/loop0", O_RDONLY)            = 3
dup2(3, 0) = 0
close(3) = 0
lseek(0, 0, SEEK_CUR) = 0
ioctl(0, MTIOCGET, 0x7fffac670080) = -1 EINVAL (Invalid argument)
lseek(0, 400, SEEK_CUR) = 400
fstat(0, {st_mode=S_IFBLK|0660, st_rdev=makedev(7, 0), ...}) = 0

which indicates dd is trying to do an MTIOCGET ioctl on your block device. man 4 st shows

The st driver provides the interface to a variety of SCSI tape devices.

[...]

MTIOCGET — get status

This request takes an argument of type (struct mtget *).

In other words, dd suspects your block device might be some variety of SCSI tape device, and asks it for the status using the ioctl() you are seeing.

It is perfectly okay for your driver to return EINVAL for this; that's what e.g. loop devices do, too.

Why IOCTL command numbers should be unique across the system?

IOCTL CMD is not necessary to be unique across the system. It should be unique for the particular device node. But the common practice is to maintain the unique CMD across system is to avoid errors caused by issuing the right command to the wrong device.

If you pass the specific command (lets say Invalid cmd for device-1) to the wrong device-2 which is capable of processing that ioctl CMD will leads to success, you will get some invalid data instead of error. To avoid this scenario we use unique CMD across system.



Related Topics



Leave a reply



Submit