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.

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:

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).

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.)

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.

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:

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.

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").

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.

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).

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.

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..

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.