Sunday, September 7, 2008

ioctls - An easy interface to talk to kernel

There are many *good* resources available on net if you want to know about ioctls. One of them is "man ioctl"
If you want to know about the list of ioctls, one quick way is "man ioctl_list". What follows below is a layman explaination and may not be liked by "strict" technical persons.

a) What are ioctls ?
ioctls are like swiss knife, one interface to do lot of things. A simple way for userspace to talk to kernel.
Among other available options (syscalls, procfs, sysfs, debugfs .. etc.), probably ioctls are the easiest one if you want to talk to kernel space as well as get that into mainline kernel. One of the reasons is because they are easy to implement and everyone follows its own convention (though it is discouraged) due to lack of no single standard.

To implement an ioctl, all you need to do is tell kernel that I will be sending some *codenumber* from userspace and depending on the codenumber you have to do something (execute a function). Much like switch-case or RPC.

b) How to implement ioctls ?
Depending on the way you want to talk to kernel, you can have an in, out, none or both arguments and you need to tell kernel accordingly so that it can take the appropriate action. Remember that you have to send a *codenumber* to kernel, so you must embed this information in it along with the functionality that you desire.

ioctls are declared using the standard macros defined in asm/ioctl.h. Macros are of type _IO(type,nr) and {_IOR,_IOW,_IOWR}(type,nr,size). Note that R and W are from user perspective (same as read & write). The last parameter 'size' is actually the datatype that needs to be transferred.

An example of ioctl by which you need to change the version of a file in kernel is :-
#define EXMPL_IOC_CHANGE_VERSION _IOW('f',7,long)

This gets expanded into a *codenumber* due to below defined macros :-
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOC(dir,type,nr,size) \
(((dir) << _IOC_DIRSHIFT) | \ /* DIRSHIFT == 30 */
((type) << _IOC_TYPESHIFT) | \ /* TYPESHIFT == 8 */
((nr) << _IOC_NRSHIFT) | \ /* NRSHIFT == 0 */
((size) << _IOC_SIZESHIFT)) /* SIZESHIFT == 16 */
# define _IOC_WRITE 1U

Based on the arguments passed this will create a unique *codenumber* which your module needs to understand. _IOC_TYPECHECK is just a macro for compiler to check for invalid uses of size argument. The ioctl number 'nr' should be chosen such that it doesn't conflict with others (that is one reason why ppl hate ioctls). Some devices use their major number for this.

Once I have defined my ioctl, I need to make kernel understand it. This is done by installing your ioctl handler in the kernel as below :-

struct file_operations my_file_operations = {
.read = my_read_func,
.write = my_write_func,
........
.ioctl = my_ioctl_handler,
........
........
}

and then define my_ioctl_handler() which performs the actual action.

long my_ioctl_handler(struct file *filp, unsigned int cmd, unsigned long arg) {
struct inode *inode = filp->f_dentry->d_inode;
unsigned int version;
switch(cmd) {
...........
case EXMPL_IOC_CHANGE_VERSION :
if (get_user(version, (int __user *) arg)) {
err = -EFAULT;
goto err_out;
}
inode->i_version = version;
return 0;
............
}


And then in userspace, you need to invoke your ioctl as :-

main() {
int fd;
fd = open("myfile",O_RDWR);
if(fd<0)
exit(1);
if(ioctl(fd,EXMPL_IOC_CHANGE_VERSION,123)))
fprintf(stderr,"Some error in ioctl ...\n")
........
}


As you can see from the example above, ioctls are very easy interface to notify kernel to do something and are largely used in device drivers. Read Documentation/ioctl-numbers.txt for more on them.

No comments: