X Input Driver HOWTO

This is a tutorial to write a new input driver for X. Knowledge of C is a prerequisite, experience with X server development is handy but not required.

Warning: This tutorial is outdated and refers to APIs that have since been changed.

If you're planning to invent a new device, you will most likely also need a matching driver for the linux kernel. This is not covered in this tutorial. Note that if you think about writing a new input driver for a “common” device (e.g. touchscreen, mouse, keyboard, etc.), please don't. Time is much better spent writing a kernel driver instead, and then your device will be picked up automatically via evdev. This tutorial is for those seeking to understand input drivers, or having special devices that cannot be used through the kernel's evdev interface.

This tutorial will explain the “random” driver for a pointing device that feeds from /dev/random. This driver will be useless. Its job is to introduce you to the way how drivers are written, not to provide an actual driver for your device. All this driver will do is randomly move the cursor along the x axis.

The full source is available from git://people.freedesktop.org/~whot/xf86-input-random. The X server and most of the drivers are kept in git. It is a good idea to familiarise yourself with git when you start developing a new driver. It is recommended that you put your own driver into a git repository, especially if you want to bundle it with the X server distributions.

Origin

This document was originally written by Peter Hutterer to have a tutorial that'll teach him how to write an input driver.

This tutorial is heavily influenced by the evdev codebase, written by Zephania E. Hull and Kristian Hogsberg.

Amendments

  • 2009-10-26: Use xf86SetStrOption instead of xf86CheckStrOption, update link to git repo (even if repo is MIA right now)

The Directory Setup

The naming convention for input drivers is xf86-input-devicename. We set the directory up, and provide the minimum amount of files. Our initial directory tree should look like this:

xf86-input-random/COPYING
                  Makefile.am
                  configure.ac
                  autogen.sh
                  man/Makefile.am
                  man/random.man
                  src/Makefile.am
                  src/random.h
                  src/random.c

We don't want to write the automake files from scratch, so we copy them from some other driver, and replace all occurances of the other driver's name with our name. The evdev driver would be a good source for this, as it is very actively maintained.

If you are compiling input drivers without the full X source tree, instead using your distribution's development libraries, you may need to install the xorg-util-macros to get the automake scripts to work properly. These macros provide the DRIVER_MAN_SUFFIX variable and the PACKAGE_VERSION_* variables amongst others.

Our source files random.h and random.c will become our driver implementation. The random.man should be your man file. Writing the man page is not covered in this tutorial.

By the way, now's a good time to init your git repository.

Some basic knowledge

An X driver has to handle three different entities: modules, drivers and devices. A module is a container for multiple drivers (In fact, our xf86-input-random is actually not a driver, but a module). The driver is responsible for setting up a device. Which driver is picked, depends on the configuration of your X server. Once a device is set up, it is responsible for posting events up to the server.

In other terms: A module is loaded once. A driver is loaded each time a section in the xorg.conf refers to it. A device is created when the driver is loading. The rest is the device's problem.

Now let's get down to hacking.

Module information

Our module needs to provide some basic information to the X server. This information is in the XF86ModuleData (xf86Module.h).

typedef pointer (*ModuleSetupProc)(pointer, pointer, int *, int *);
typedef void (*ModuleTearDownProc)(pointer);

typedef struct {
    XF86ModuleVersionInfo *     vers;
    ModuleSetupProc             setup;
    ModuleTearDownProc          teardown;
} XF86ModuleData;

The first field of the struct is the module's version info. I won't go into details explaining it, just copy it from some driver and replace the name. Be reminded that this information has to be available before any of your module's functions are called, so put it as a global static into your file.

The ModuleSetupProc is called when the module is loaded. Any module-specific data initialisation needs to go in here.

The ModuleTearDownProc will be called when our module is unloaded. Anything ever allocated by our module needs to be freed here.

With this information, we can write our first few lines of code. (Copyright headers and #includes are skipped for brevity. Please refer to the full source code.)

static XF86ModuleVersionInfo RandomVersionRec =
{
    "random",
    MODULEVENDORSTRING,
    MODINFOSTRING1,
    MODINFOSTRING2,
    XORG_VERSION_CURRENT,
    PACKAGE_VERSION_MAJOR, PACKAGE_VERSION_MINOR,
    PACKAGE_VERSION_PATCHLEVEL,
    ABI_CLASS_XINPUT,
    ABI_XINPUT_VERSION,
    MOD_CLASS_XINPUT,
    {0, 0, 0, 0}
};

_X_EXPORT XF86ModuleData randomModuleData =
{
    &RandomVersionRec,
    RandomPlug,
    RandomUnplug
};

static void
RandomUnplug(pointer p)
{
}

static pointer
RandomPlug(pointer        module,
           pointer        options,
           int            *errmaj,
           int            *errmin)
{
    xf86AddInputDriver(&RANDOM, module, 0);
    return module;
}

All the defines in the VersionInfo will be replaced by our automake build scripts.

Driver information

In the previous listing you can see the call to xf86AddInputDriver(). This call tells X information about a new available driver. As said before, a module may contain multiple drivers.

The RANDOM variable we refer simply contains the information we to initialize our driver. Its of type InputDriverRec.

typedef struct _InputDriverRec {
    int                     driverVersion;
    char *                  driverName;
    void                    (*Identify)(int flags);
    struct _LocalDeviceRec *(*PreInit)(struct _InputDriverRec *drv,
                                       IDevPtr dev, int flags);
    void                    (*UnInit)(struct _InputDriverRec *drv,
                                      struct _LocalDeviceRec *pInfo,
                                      int flags);
    pointer                 module;
    int                     refCount;
} InputDriverRec, *InputDriverPtr;

Some of the fields here are important, others aren't quite as important. Identify is never called for input drivers anyway, so none of the X.org input drivers use it. All drivers use a driverVersion of 1.

Other fields are more important. driverName needs to be substituted with our driver's name. This doesn't have to be the same name as the module. The driver name is what you will need to refer to it in the Section "InputDevice" in your xorg.conf. PreInit will be called when your device has been added to the server. It will be called once per device and allows you to initialize internal structs you need for your driver to function correctly. UnInit will be called when your device is removed from the server. Clean up your mess here.

module and refCount are used by the server. Don't touch them.

For our driver, the code looks like this:

static InputInfoPtr
RandomPreInit(InputDriverPtr  drv,
              IDevPtr         dev,
              int             flags)
{
    return NULL;
}

static void
RandomUnInit(InputDriverPtr       drv,
             InputInfoPtr         pInfo,
             int                  flags)
{
}

_X_EXPORT InputDriverRec RANDOM = {
    1,
    "random",
    NULL,
    RandomPreInit,
    RandomUnInit,
    NULL,
    0
};

With all this code in place we already have a driver that compiles and can be loaded up. However, it won't do anything, except wasting a few CPU cycles. And it's not particularly good at that either.

Initialising a new device

In this section we'll discuss what should happen in the PreInit and UnInit.

PreInit is called when a new device is added to the server. This can happen at server startup (the device has an entry in the config file) or during runtime (hotplugged). For us as driver developers this doesn't really matter. Note that PreInit will be called for each device using our driver!

What the PreInit should do:

  • an InputInfoRec needs to be allocated. This struct will contain important information about our device.
  • configuration options need to be parsed
  • the device should be tested for availability and accessability.
  • internal data needs to be initialised, if the driver needs to. I'll just throw some code at you:
static InputInfoPtr RandomPreInit(InputDriverPtr  drv,
                                  IDevPtr         dev,
                                  int             flags)
{
    InputInfoPtr        pInfo;
    RandomDevicePtr     pRandom;

    if (!(pInfo = xf86AllocateInput(drv, 0)))
        return NULL;

    pRandom = xcalloc(1, sizeof(RandomDeviceRec));
    if (!pRandom) {
        pInfo->private = NULL;
        xf86DeleteInput(pInfo, 0);
        return NULL;
    }

    pInfo->private = pRandom;

First we let the server allocate information, then we allocate a RandomDeviceRec for our own information and attach it to the device's private field. The RandomDeviceRec (look at the source) is just the struct we need to store device-internal stuff for our driver. Our driver is so simple, it won't contain much. The more complicated your driver is, the more you will have to store in this private struct.

If an error occurs, PreInit should clean up and return NULL.

    pInfo->name = xstrdup(dev->identifier);
    pInfo->flags = 0;
    pInfo->type_name = XI_MOUSE; /* see XI.h */
    pInfo->conf_idev = dev;
    pInfo->read_input = RandomReadInput; /* new data avl */
    pInfo->switch_mode = NULL; /* toggle absolute/relative mode */
    pInfo->device_control = RandomControl; /* enable/disable dev */

Important for us are

  • read_input: will be called when new data is available.
  • switch_mode: will be called when a client requests to switch between absolute and relative mode of a device. (Not applicable for our random device).
  • device_control: will be called when the device is switched on or off. Now we need to process some options.
    /* process driver specific options */
    pRandom->device = xf86SetStrOption(dev->commonOptions,
                                         "Device",
                                         "/dev/random");

    xf86Msg(X_INFO, "%s: Using device %s.\n", pInfo->name, pRandom->device);

    /* process generic options */
    xf86CollectInputOptions(pInfo, NULL, NULL);
    xf86ProcessCommonOptions(pInfo, pInfo->options);

So first we check for a driver-specific options. If the "Device" option is set, we extract it (if not, we use the default provided). The same interface is available to get other data types. xf86Set{Int|Real|Str|Bool}Option(), returns us the value we need (with an optional default provided). Using xf86SetStrOption marks the option as used and also prints it to the log file.

After we're done processing our special options, we just tell the server to process all generic options (e.g. "SendCoreEvents").

Now we should test if the device is available and accessible.

    /* Open sockets, init device files, etc. */
    SYSCALL(pInfo->fd = open(pRandom->device, O_RDWR | O_NONBLOCK));
    if (pInfo->fd == -1)
    {
        xf86Msg(X_ERROR, "%s: failed to open %s.",
                pInfo->name, pRandom->device);
        pInfo->private = NULL;
        xfree(pRandom);
        xf86DeleteInput(pInfo, 0);
        return NULL;
    }

    /* do more funky stuff */

    close(pInfo->fd);
    pInfo->fd = -1;

We use SYSCALL to make sure our call isn't interrupted. Once the device is open, you may want to do some additional funky stuff (have a look at evdev) to get information from the device. Finally we close the file again, after all it was just a test to see if it's there.

Let's set some flags and finish up. Note that if we don't set the XI86_CONFIGURED flag, the server thinks something failed and will clean up our device.

    pInfo->flags |= XI86_OPEN_ON_INIT;
    pInfo->flags |= XI86_CONFIGURED;

    return pInfo;
}

The UnInit is not overly exciting and doesn't need further explanation.

static void RandomUnInit(InputDriverPtr drv,
                         InputInfoPtr   pInfo,
                         int            flags)
{
    RandomDevicePtr       pRandom = pInfo->private;

    if (pRandom->device)
    {
        xfree(pRandom->device);
        pRandom->device = NULL;
    }

    xfree(pRandom);
    xf86DeleteInput(pInfo, 0);
}

Recap: we now have a driver that initialises and tests for our device on startup.

Starting and stopping our device

Key to using our device is starting and stopping it when necessary. If your driver does not do this, it may cause havoc when your device is hotplugged or removed at runtime.

The device_control field in the InputInfoRec is used to notify a device about state changes. It takes two arguments, the first being the device, the second being what the device should do (one out of DEVICE_INIT, DEVICE_ON, DEVICE_OFF, DEVICE_CLOSE).

Again, here's some code.

static int RandomControl(DeviceIntPtr    device,
                         int             what)
{
    InputInfoPtr  pInfo = device->public.devicePrivate;
    RandomDevicePtr pRandom = pInfo->private;

    switch(what)
    {
        case DEVICE_INIT:
            _random_init_buttons(device);
            _random_init_axes(device);
            break;

DEVICE_INIT is called when the device is added to the server. The server expects the device to allocate all necessary information to figure out what type of device it is.

Here's the code we use to init a device hardcoded to two buttons. Your device should include some option to switch the button map around, but we don't bother for it here.

static int
_random_init_buttons(DeviceIntPtr device)
{
    InputInfoPtr        pInfo = device->public.devicePrivate;
    CARD8               *map;
    int                 i;
    int                 ret = Success;
    const int           num_buttons = 2;

    map = xcalloc(num_buttons, sizeof(CARD8));

    for (i = 0; i < num_buttons; i++)
        map[i] = i;

    if (!InitButtonClassDeviceStruct(device, num_buttons, map)) {
            xf86Msg(X_ERROR, "%s: Failed to register buttons.\n", pInfo->name);
            ret = BadAlloc;
    }

    xfree(map);
    return ret;
}

And here's the code to init a device hardcoded to two axes. And we're hardcoded to Relative mode too.

static int
_random_init_axes(DeviceIntPtr device)
{
    InputInfoPtr        pInfo = device->public.devicePrivate;
    int                 i;
    const int           num_axes = 2;

    if (!InitValuatorClassDeviceStruct(device,
                num_axes,
                GetMotionHistory,
                GetMotionHistorySize(),
                0))
        return BadAlloc;

    pInfo->dev->valuator->mode = Relative;
    if (!InitAbsoluteClassDeviceStruct(device))
            return BadAlloc;

    for (i = 0; i < pRandom->axes; i++) {
            xf86InitValuatorAxisStruct(device, i, -1, -1, 1, 1, 1);
            xf86InitValuatorDefaults(device, i);
    }

    return Success;
}

In general, a driver does not need to act in any way with pointer acceleration. The acceleration is initialized during InitValuatorClassDeviceStruct, user settings on acceleration are loaded when a driver calls xf86InitValuatorDefaults. But of course it depends on what you want, and a small API is available, as described in Pointer acceleration.

Back to the device control. We continue with the switch statement.

DEVICE_ON is the signal that we should open the device now and start sending events.

            /* Switch device on.  Establish socket, start event delivery.  */
            case DEVICE_ON:
                xf86Msg(X_INFO, "%s: On.\n", pInfo->name);
                if (device->public.on)
                        break;

                SYSCALL(pInfo->fd = open(pRandom->device, O_RDONLY | O_NONBLOCK));
                if (pInfo->fd < 0)
                {
                    xf86Msg(X_ERROR, "%s: cannot open device.\n", pInfo->name);
                    return BadRequest;
                }

                xf86FlushInput(pInfo->fd);
                xf86AddEnabledDevice(pInfo);
                device->public.on = TRUE;
                break;

xf86AddEnabledDevice() will add our device's fd to the list of SIGIO handlers. When a SIGIO occurs, our read_input will get called. And of course we need to mark the device as being switched on, so the server knows we're doing our work here.

DEVICE_OFF should close the device, DEVICE_CLOSE free whatever is left..

           case DEVICE_OFF:
                xf86Msg(X_INFO, "%s: Off.\n", pInfo->name);
                if (!device->public.on)
                    break;
                xf86RemoveEnabledDevice(pInfo);
                close(pInfo->fd);
                pInfo->fd = -1;
                device->public.on = FALSE;
                break;
          case DEVICE_CLOSE:
                /* free what we have to free */
                break;
    }
    return Success;
}

Note that a client can cause DEVICE_ON and DEVICE_OFF to be called repeatedly. DEVICE_CLOSE however signals that the device is to be removed now and will not be switched on again.

Well, that's basically it. All we need to do now is read data and post events.

Reading Input

We know that our read_input will be called when data is available. So that's all we need to do now.

static void RandomReadInput(InputInfoPtr pInfo)
{
    char                  data;

    while(xf86WaitForInput(pInfo->fd, 0) > 0)
    {
        read(pInfo->fd, &data, 1);

        xf86PostMotionEvent(pInfo->dev,
                            0, /* is_absolute */
                            0, /* first_valuator */
                            1, /* num_valuators */
                            data);
    }
}

So each time we get an interrupt, we read one byte of the device and pass it on as a motion event with an attached x value. The pointer will erratically move around on the x-axis (You may need to move your other mouse around, so /dev/random will generate enough data).

Now of course you need to do error checking, or some more sophisticated event generation. The DDX interfaces for posting events are: xf86PostMotionEvent(); xf86PostButtonEvent(); xf86PostKeyboardEvent(); xf86PostProximityEvent(); xf86PostKeyEvent(); (for key events with attached valuator values)

You can find them in the xf86Xinput.h.

When you're posting an event, the server takes care of creating XInput events and puts all events onto the event queue. At a later point in time, the server will take them out and process them. If you're feeding too many events in too short time, the queue might overrun and events will get lost. This is generally not a problem with input drivers, unless the server is really really busy with drawing. Building a /dev/urandom driver may flood the server though.

The Big Picture

Let's recap. We have modules. Modules can provide multiple drivers, each with their own capabilities. We have drivers. Drivers reside in modules and are responsible for setting up and talking to devices.

We have devices. Each device should be independent of each other, and they can have different read_input/device_control/etc. procs. The device's read_input will be called when data is available. The device's device_control will be called when the device is activated/deactivated etc.

A device needs to post events to the DDX, and from then on it's the responsibility of the server to do the right job.

Final words

That's it. You should now understand how a driver works and be able to write your own. Good luck.