Lin­ux­u­la­tor explained (for devel­op­ers): adding ioctls direct­ly to the kernel

After giv­ing an overview of the in-kernel basics of the Lin­ux­u­la­tor, I want now to describe how to add sup­port for new ioctls to the Linuxulator.

Where are the files to modify?

The plat­form inde­pen­dent 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 mod­i­fy them?

First of all cre­ate a new head­er which will con­tain all the struc­tures, named val­ues and macros for those new ioctls. As writ­ten above, the ioctl val­ues (e.g. #define LINUX_VIDIOC_ENCODER_CMD 0x564d /* 0xc028564d */) do not belong there, they shall be added 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/ direc­to­ry, and pre­fix the name with a linux_. It would be good to decide on a com­mon tag here (ref­er­enced as yourtag in the fol­low­ing), and stay with it. Use it wher­ev­er you need to have some spe­cif­ic name for the ioctl-set you want to add. In this case it would result in linux_yourtag.h (or even linux_ioctl_yourtag.h, depend­ing if this is used for some­thing very spe­cif­ic to the ioctls, or some gener­ic lin­ux fea­ture) as the name of the head­er file. This was not done in the past, so do not expect that the names inside the linux_ioctl.c file will be con­sis­tent 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 development).

Now add this head­er to linux_ioctl.c (you need to include 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 lin­ux, but with a LINUX_ pre­fix (make sure they where not defined before some­where else). The ioctl val­ues need to be the same hex val­ues as in Lin­ux, off course. Sort them accord­ing to their hex val­ue. When you added all, you need to add two more defines. The LINUX_IOCTL_yourtag_MIN and LINUX_IOCTL_yourtag_MAX ones. The MIN-one needs to be an alias for the first (sort­ed accord­ing to the hex val­ue) ioctl you added, and MAX needs to be an alias for the last (again, sort­ed accord­ing to the hex val­ue) ioctl you added.

The next step is to let the Lin­ux­u­la­tor know that it is able to han­dle the ioctls in the LINUX_IOCTL_yourtag_MIN to LINUX_IOCTL_yourtag_MAX range. Search the sta­t­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_yourtag.

Sim­i­lar for the handler-definition for this. Search the sta­t­ic struct linux_ioctl_handler sec­tion and add a yourtag_handler. Set it to { linux_ioctl_yourtag, LINUX_IOCTL_yourtag_MIN, LINUX_IOCTL_yourtag_MAX }. To make this han­dler known to the Lin­ux­u­la­tor, you need to add it to the DATA_SET sec­tion. Add DATA_SET(linux_ioctl_handler_set, yourtag_handler) there.

Now the meat, the func­tion which han­dles 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:

static int
linux_ioctl_yourtag(struct thread *td, struct linux_ioctl_args *args)
{
        struct file *fp;
        int error;
        switch (args->cmd & 0xffff) {
        case LINUX_an_easy_ioctl:
                break;
        case LINUX_a_not_so_easy_ioctl:
                /* your handling of the ioctl */
                fdrop(fp, td);
                return (error);
        /* some more handling of your ioctls */
        default:
       return (ENOIOCTL);
        }
        error = ioctl(td, (struct ioctl_args *)args);
        return (error);
}

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 FreeB­SD itself. 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 exam­ple, there are also oth­er pos­si­bil­i­ties where you need to do addi­tion­al stuff before the return, or where you do not pass the ioctl to FreeB­SD. A typ­i­cal exam­ple of what needs to be done here is to copy val­ues from lin­ux struc­tures to FreeB­SD struc­tures (and the oth­er way too), or to trans­late between 64bit and 32bit. Lin­ux pro­grams on amd64 are 32bit exe­cuta­bles and 32bit structures/pointers. To make this work on amd64, you need to find a way to map between the two. There are exam­ples in the ker­nel where this is already the case. The more promi­nent exam­ples in the 64bit<->32bit regard are the v4l and v4l2 ioctls.

The tedious part is to research if a trans­la­tion has to be done and if yes what needs to be trans­lat­ed 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­si­ble to add ioctls in a ker­nel mod­ule, but this is not sub­ject to this descrip­tion (I will update this post­ing with a link to it when I get time to write about it).

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.