Linuxu­lat­or ex­plained (for de­velopers): adding ioctls dir­ectly to the ker­nel

After giv­ing an over­view of the in-​kernel ba­sics of the Linuxu­lat­or, I want now to de­scribe how to add sup­port for new ioctls to the Linuxu­lat­or.

Where are the files to modi­fy?

The plat­form in­de­pend­ent code for the ioctls is in SRC/​sys/​compat/​linux/linux_ioctl.c. The defines to have names for the ioctl val­ues are in SRC/​sys/​compat/​linux/linux_ioctl.h.

How to modi­fy them?

First of all cre­ate a new head­er which will con­tain all the struc­tures, named val­ues and mac­ros for those new ioctls. As writ­ten above, the ioctl val­ues (e.g. #define LINUX_​VIDIOC_​ENCODER_​CMD 0x564d /​* 0xc028564d */​) do not be­long there, they shall be ad­ded to linux_ioctl.h. Dur­ing the course of adding sup­port for ioctls, you will need this new head­er. Add it in the SRC/​sys/​compat/​linux/​ dir­ect­ory, and pre­fix the name with a linux_​. It would be good to de­cide on a com­mon tag here (ref­er­enced as your­tag in the fol­low­ing), and stay with it. Use it wherever you need to have some spe­cif­ic name for the ioctl-​set you want to add. In this case it would res­ult in linux_​your­tag.h (or even linux_​ioctl_​your­tag.h, de­pend­ing if this is used for some­thing very spe­cif­ic to the ioctls, or some gen­er­ic linux fea­ture) as the name of the head­er file. This was not done in the past, so do not ex­pect that the names in­side the linux_ioctl.c file will be con­sist­ent to this nam­ing scheme, but it is nev­er too late to cor­rect mis­takes of the past (at least in Open Source soft­ware de­vel­op­ment).

Now add this head­er to linux_ioctl.c (you need to in­clude compat/linux/linux_yourtag.h). After that add the ioctl val­ues to linux_ioctl.h. As can be seen above, the defines should be named the same as on linux, but with a LINUX_​ pre­fix (make sure they where not defined be­fore some­where else). The ioctl val­ues need to be the same hex val­ues as in Linux, off course. Sort them ac­cord­ing to their hex value. When you ad­ded all, you need to add two more defines. The LINUX_​IOCTL_​your­tag_​MIN and LINUX_​IOCTL_​your­tag_​MAX ones. The MIN-​one needs to be an ali­as for the first (sor­ted ac­cord­ing to the hex value) ioctl you ad­ded, and MAX needs to be an ali­as for the last (again, sor­ted ac­cord­ing to the hex value) ioctl you ad­ded.

The next step is to let the Linuxu­lat­or know that it is able to handle the ioctls in the LINUX_​IOCTL_​your­tag_​MIN to LINUX_​IOCTL_​your­tag_​MAX range. Search the stat­ic linux_​ioctl_​function_​t sec­tion of linux_ioctl.c and add such a vari­able for your ioctl set. The name of the vari­able should be some­thing like linux_​ioctl_​your­tag.

Sim­il­ar for the handler-​definition for this. Search the stat­ic struct linux_​ioctl_​handler sec­tion and add a yourtag_​handler. Set it to { linux_​ioctl_​your­tag, LINUX_​IOCTL_​your­tag_​MIN, LINUX_​IOCTL_​your­tag_​MAX }. To make this hand­ler known to the Linuxu­lat­or, you need to add it to the DATA_​SET sec­tion. Add DATA_SET(linux_ioctl_handler_set, your­tag_​handler) there.

Now the meat, the func­tion which handles the ioctls. You already defined it as linux_​ioctl_​function_​t, but now you need to write it. The out­line of it looks like this:

stat­ic int
linux_​ioctl_​your­tag(struct thread *td, struct linux_​ioctl_​args *args)
        struct file *fp;
        int er­ror;
        switch (args->cmd & 0xffff) {
        case LINUX_​an_​easy_​ioctl:
        case LINUX_​a_​not_​so_​easy_​ioctl:
                /​* your hand­ling of the ioctl */
                fdrop(fp, td);
                re­turn (er­ror);
        /​* some more hand­ling of your ioctls */
       re­turn (ENOIOCTL);
        er­ror = ioctl(td, (struct ioctl_​args *)args);
        re­turn (er­ror);

An easy ioctl in the switch above is an ioctl where you do not have to do some­thing but can pass the ioctl through to FreeBSD it­self. The not so easy ioctl case is an ioctl where you need to do e.g. a fget(td, args->fd, &fp). This is just an ex­ample, there are also oth­er pos­sib­il­it­ies where you need to do ad­di­tion­al stuff be­fore the re­turn, or where you do not pass the ioctl to FreeBSD. A typ­ic­al ex­ample of what needs to be done here is to copy val­ues from linux struc­tures to FreeBSD struc­tures (and the oth­er way too), or to trans­late between 64bit and 32bit. Linux pro­grams on amd64 are 32bit ex­ecut­ables and 32bit structures/​pointers. To make this work on amd64, you need to find a way to map between the two. There are ex­amples in the ker­nel where this is already the case. The more prom­in­ent ex­amples in the 64bit<->32bit re­gard are the v4l and v4l2 ioctls.

The te­di­ous part is to re­search if a trans­la­tion has to be done and if yes what needs to be trans­lated how. When this is done, most of the work is not so hard. The linux_yourtag.h should con­tain the struc­tures you need for this trans­la­tion work.

It is also pos­sible to add ioctls in a ker­nel mod­ule, but this is not sub­ject to this de­scrip­tion (I will up­date this post­ing with a link to it when I get time to write about it).

Leave a Reply

Your email address will not be published. Required fields are marked *