Controlling Input/Output of Device Drivers

Hands-on tutorial for monitoring and configuring I/O for network device drivers at the Kernel level.

In this article I show how to control input/output (I/O) of device drivers. Device I/O control is performed by calling driver functions in the kernel. These functions available vary from device to device. As a result, some investigation of the system header files are required in order understand how to call them and interpret the results they return.

Kernel functions are accessed via system calls, however, instead of introducing new systems calls for each I/O function, ioctl is provided as a generic system call for all device I/O control. Network interface configuration commands, for example, make extensive use of the ioctl system call. Try running strace ifconfig eth0 (for example) and you will see a number of calls to ioctl. This article focuses on network devices (serial port, Ethernet cards and Wireless cards) and gives examples of how to monitor and configure them at the system call level.

The ioctl System Call

A device driver consists of a set of kernel space functions that can be accessed from user space via system calls. These functions are implemented to resemble the file system interface. The file operations struct, defined in include/linux/fs.h under the kernel source tree, gives a list of the functions. A driver not may implement all these functions (unwanted functions are defined as NULL).

However, it is likely that the driver will require a number of extra functions for input/output control. Furthermore, these functions tend to be specific to the particular device. For example, a wireless device needs a function to set the access point MAC address, but such a function is unnecessary on an Ethernet card. Rather than introducing new system calls into the kernel; a single system call, ioctl, is used to call device specific functions. The synopsis for ioctl (from the man page) is:

int ioctl(int fildes, int request, /*arg*/...);

The first argument (filedes) is the open file descriptor of the device. For network devices, such as Ethernet and wireless cards, a socket descriptor is used The second argument (request specifies the requested function (as an integer value). ioctl functions are defined as macros in the header files under /usr/include. Traditionally, in Unix, the choice of ioctl function numbers were somewhat ad hoc. However, if drivers shared the same function numbers, an ioctl call to an incorrect device could result in some undesirable outcome.

For this reason Linux adopts a numbering convention in order to ensure function numbers are” globally unique”. A call to the wrong driver returns a EINVAL error rather than some random result. The third argument is an untyped pointer to some memory location in user space which is used to exchange data between driver and application. The structure of this memory location will depend upon the function being called. The most useful source of information on driver function data structure are the header files in /usr/include.

The Terminal

For the first example, I’ll use ioctl to get the line speed of a tty. This can be done from the command line using the stty command. The sequence of commands below tells me the current line speed of my (pseudo) tty is set to 38400 baud (albeit, this is pretty meaningless for a pseudo tty):

$ tty
$ stty speed </dev/pts/0

The state information for a terminal device (even a pseudo one) is held in variable of type termio. The definition of termio is found in bits/ioctl-types.h:

struct termio
    unsigned short int c_iflag;
    unsigned short int c_oflag;
    unsigned short int c_cflag;
    unsigned short int c_lflag;
    unsigned char c_line;
    unsigned char c_cc[NCC];

For this example, we are only interested in the c_cflag field, the four least significant bits of which store the line speed of the terminal. The TCGETA function is used to get terminal state information. The macro is defined in the kernel source at asm-i386/ioctls.h:

#define TCGETA 0x5405

We define desc as a termio structure which is then passed as pointer in ioctl call:

struct termio desc;

The value of the line speed is given by:

line_speed = desc.c_cflag & (unsigned int) 15

The value of line_speed should correspond to a value of one of the macros defined in bits/termios.h:

#define  B50    0000001
#define  B75    0000002
#define  B110   0000003
#define  B134   0000004
#define  B150   0000005
#define  B200   0000006
#define  B300   0000007
#define  B600   0000010
#define  B1200  0000011
#define  B1800  0000012
#define  B2400  0000013
#define  B4800  0000014
#define  B9600  0000015
#define  B19200 0000016
#define  B38400 0000017

Once you’ve got the code in place, use this command to compile it:

$ gcc -o get_line_speed get_line_speed.c

Now run it:

$ ./get_line_speed /dev/pts/0
[./get_line_speed] /dev/pts/0 is 38400 baud

In this next example we write a program to set the line speed. The ioctl function for this is TCSETA, and is defined in asm-i386/ioctls.h:

#define TCSETA 0x5406

To set the line speed of the terminal device, first we have to issue a TCGETA, in order to save the current state of terminal. The reason for doing this is that the state of the other terminal characteristics must be preserved when we change the line speed. Having read the terminal characteristics (into desc), we mask out the line speed bits:

desc.c_cflag &= 0x02DCCBAUD;

The baud rate is set (in this case to 9600 baud) and then TCSETA is called:

desc.c_cflag |= B9600;
ioctl(fd,TCSETA, &desc);

The full code listing for setting the line speed setting is in Listing 2. Compile set_line_speed.c, then set the line speed (where the baud rate of the line is passed as a command-line argument) with:

$ ./set_line_speed /dev/pts/0 9600

Confirm that the line speed has changed:

$ ./get_line_speed /dev/pts/0
[./get_line_speed] /dev/pts/0 is 9600 baud

Double check using stty speed>/dev/pts/0. Note that it’s recommended that you use the cfgetispeed, cfgetospeed, cfsetispeed, and cfsetospeed macros for setting line speeds, rather than calling ioctl directly.

Network Interfaces

ioctl calls can be issued to network interfaces using an open socket descriptor (instead of a file descriptor). The line of code shown here will suffice:

sd = socket(AF_INET, SOCK_DGRAM, 0)

This example shows how to get a list of all the network interfaces for your machine. This is done by issuing the SIOCGIFCONF function, which is defined in bits/ioctl.h (and in linux/sockios.h for some reason):

#define SIOCGIFCONF 0x8912

The SIOCGICONF function requires to important data structures, ifconf and ifreq (defined in linux/if.h). The ifconf struct is defined as:

struct ifconf
    int     ifc_len;
        char *ifcu_buf;
        struct ifreq *ifcu_req;
    } ifc_ifcu;

The SIOCGIFCONF function returns a number of records of type ifreq (one for each interface) in a buffer pointed to by conf.ifc_buf. Then ioctl is called with a pointer to conf:

struct ifreq
            char    ifrn_name[IFNAMSIZ];
    } ifr_ifrn;

    union {
        struct  sockaddr ifru_addr;
        struct  sockaddr ifru_dstaddr;
        struct  sockaddr ifru_broadaddr;
        struct  sockaddr ifru_netmask;
        struct  sockaddr ifru_hwaddr;
        short   ifru_flags;
        int     ifru_ivalue;
        int     ifru_mtu;
        struct  ifmap ifru_map;
        char    ifru_slave[IFNAMSIZ];
        char    ifru_newname[IFNAMSIZ];
        void *  ifru_data;
        void *  ifru_data;
        struct  if_settings ifru_settings;
    } ifr_ifru;

The SIOCGIFCONF function returns a number of records of type ifreq (one for each interface) in a buffer pointed to by conf.ifc_buf. Then, ioctl is called with a pointer to conf:

struct ifconf conf;
char if_data[1024];
conf.ifc_len = sizeof(if_data);
conf.ifc_buf = if_data;

ioctl(sd, SIOCGIFCONF, &conf);

The code listing for show_ints.c is in Listing 3. Run it, having compiled it first, with:

$ ./show_ints
[./show_ints] lo eth0 eth1

It shows that my machine has a loop back interface and two” Ethernet” interfaces, where eth0 is an Ethernet device and eth1 is a wireless device.

This next example shows how to set the IP address and subnet mask on an interface. The macros for these two functions (defined in linux/sockios.h) are:

#define SIOCSIFADDR     0x8916
#define SIOCSIFNETMASK  0x891c

Listing 4 shows the code for set_ipaddr.c. As root, set the IP address and network mask:

$ sudo ./set_ipaddr eth0>

Then confirm that the IP address and subnet mask have been set:

$ ifconfig eth0 | grep inet
inet addr: Bcast: Mask:

Wireless Interfaces

Next, let’s focus on functions specific to the wireless card. The first example shows how to get the ESSID of the associated network. The function for getting the ESSID is defined in linux/wireless.h:


The important data structures for wireless ioctl calls are iwreq and iwreq_data (linux/wireless.h). The definition of the iwreq struct is:

struct  iwreq
        char    ifrn_name[IFNAMSIZ];
    } ifr_ifrn;

   union   iwreq_data      u;

The iwreq_data struct is shown below. For brevity, some of the fields have been omitted:

union   iwreq_data
    char            name[IFNAMSIZ];
    struct iw_point essid;   /* Extended network name */
    struct iw_param rts;     /* RTS threshold threshold */
    struct iw_param frag;    /* Fragmentation threshold */
    struct iw_param param;   /* Other small parameters */
    struct iw_point data;    /* Other large parameters */

Certain fields in the data structures are assigned prior to the ioctl call. The ESSID is written to the memory location referenced by w.u.essid.pointer, thus:

struct iwreq w;
char essid[IW_ESSID_MAX_SIZE];
strncpy(w.ifr_ifrn.ifrn_name, ifname, IFNAMSIZ);
w.u.essid.pointer = (caddr_t *) essid;
w.u.data.length = IW_ESSID_MAX_SIZE;
w.u.data.flags = 0;
ioctl(sd, SIOCGIWESSID, &w);

The code for get_essid.c is in Listing 5 (again, found online at http://www.linux-mag.com/code/08.03.ioctl.tar). Running this command on a wireless device current associated with a network, gives:

$ ./get_essid eth1
[./get_essid] essid for eth1: mywifi

You can confirm this result with the command:

$ iwconfig eth1 | grep ESSID
eth1 IEEE 802.11g ESSID:"mywifi"

If you run this command on an Ethernet device (such as eth0 in my case) you will get a” Operation not supported” error. These last two examples get and set the RTS and Fragmentation thresholds. The corresponding macros are defined as:

#define SIOCSIWRTS  0x8B22 /* set RTS/CTS threshold (bytes) */
#define SIOCGIWRTS  0x8B23 /* get RTS/CTS threshold (bytes) */
#define SIOCSIWFRAG 0x8B24 /* set fragmentation thr (bytes) */
#define SIOCGIWFRAG 0x8B25 /* get fragmentation thr (bytes) */

The code for get_rts_frag.c and set_rts_frag.c are shown in Listings 6 and 7 respectively. Once both have been compiled, run get_rts_frag on the wireless device:

$ ./get_rts_frag eth1
[./get_rts_frag] rts threshold: 2347 (disabled)
[./get_rts_frag] fragmentation threshold: 2346 (disabled)

It shows the RTS and Fragmentation thresholds are 2347 (w.u.rts.value) and 2346 (w.u.frag.value) bytes respectively. Also note that RTS and Fragmentation are disabled (w.u.rts.disabled and w.u.frag.disabled set to 1). We assign the thresholds (from values passed on the command line) prior to calling ioctl. Note that we also have to enabled RTS and Fragmentation:

w.u.rts.disabled = 0;
w.u.rts.value = atoi(rts_value);
ioctl(sd, SIOCSIWRTS, &w); w.u.frag.disabled = 0;
w.u.frag.value = atoi(frag_value);
ioctl(sd, SIOCSIWFRAG, &w);

Run set_rts_frag as root:

$ sudo ./set_rts_frag eth1 2000 2000

Now use get_rts_frag to confirm that the thresholds have been set:

$ ./get_rts_frag eth1
[./get_rts_frag] rts threshold: 2000 (enabled)
[./get_rts_frag] fragmentation threshold: 2000 (enabled)

Double check with the iwconfig command:

$ iwconfig eth1 | grep RTS
Retry limit:15 RTS thr:2000 B Fragment thr:2000 B


Functions for I/O control of device drivers are specific to the device. Rather than developing new system calls in the kernel for each driver, ioctl is used calling driver specific functions. Data structures passed between kernel and user space are also device dependent so it is important to examine the header files in /usr/include for clues on how to call ioctl functions correctly.

The ioctl functions can be issued on devices using file or socket descriptors. I’ve provided number of examples I/O control on communication devices have been presented in this article, and I hope that you can extrapolate those into something useful for your own projects.

Comments on "Controlling Input/Output of Device Drivers"


I guess a prerequisite to reading this article would be a basic understanding of coding in c of which I have none of. This stuff is way over my head.


nice i hope that we can see more in depth articles like this, Ph. Alan ;) please do it frequently :) .

rakesh uv

Hi Alan,
That was a wonderful and simple explaination, Keep it UP.
Please Do Post Such Articles frequently.
There one problem with one of the links provided in the article,http://www.linux-mag.com/code/08.03.ioctl.tar.

Thank You


Thanks for the feedback Rakesh. The article appeared in Linux Magazine a couple of months ago, so they are responsible for publishing the article on-line. To save space the editor took the code listings out and put them on the web. I don’t know why the URL doesn’t work, but I’ve let them know – hopefully it will be fixed soon. However, as an alternative, I’ve put the tar file here: http://agholt.googlepages.com/ioctl.tar.gz


Just to be clear… the show_ints code can only see interfaces which are in an ‘up’ state.

If you were truly doing interface discovery (i.e. for an installer or some such) and no interfaces are in a configured state, what would the code look like to identify the interfaces?


Yes you are correct, I probably should have made that clear in the article. I’m just really trying to demonstrate ioctls calls rather write a fully fledged “interface discovery” utility. If I were, I’d probably just get them out of /proc/net/dev. But thanks for the clarification.


Ok. I didn’t know whether ioctl could be used in some other way to obtain the ‘down’ interfaces as well. That was more my reason for asking. I wasn’t (intentionally) trying to be annoyingly pedantic.


Cirnath: I didn’t think you were trying to be pedantic – it’s a good point. Browsing through the header files SIOCGIFCONF is the only function I see that returns a list of interfaces and, as you correctly point out, it doesn’t return down interfaces – if it doesn’t have an IP address. A down interface with an IP address does show up (how weird is that?) I guess the behaviour of SIOCGIFCONF is down to the whim of the kernel developers.

“Thanks a lot for the blog.”

good post,it is useful to me and others,please just keep it on….
Wholesale 8894 a Oakley Sunglasses big (7) http://www.fleetsale.ru/new-arrival-oakleys-469.html

As a consequence of you for the tolerable writeup. It in in truth was a distraction account it. Look advanced to more added satisfactory from you! At any rate, how could we communicate?

Leave a Reply