Caution: This version of this document is no longer maintained. For the latest documentation, see http://www.qnx.com/developers/docs.

Writing an Input Device Driver

In this chapter...

Creating an input module

To write an input driver, you must first create your own input module. The sample directory contains a sample skeleton for creating a module. We recommend that you use this as a starting point.

A module is represented by a data type called input_module_t. It contains various data fields and function pointers representing its interface.

input_module_t data type

Writing an input module consists of simply creating an input_module_t representing your module and filling in the relevant interface functions.

struct _input_module {

    input_module_t   *up;        // Up and down modules in bus line -
    input_module_t   *down;      // for internal use only
    struct Line      *line;      // driver bus line - for internal use only
    int               flags;     // module flags
    int               type;      // type of module
    char              name[12];  // module name (used in devi-* commands)
    char              date[12];  // date of compilation
    const char       *args;      // list of module args (used in devi-* commands)
    void             *data;      // private module data
    // pointers to user-supplied module functions
    int             (*init)(input_module_t *);
    int             (*reset)(input_module_t *);
    int             (*input)(input_module_t *, int, void *);
    int             (*output)(input_module_t *, void *, int);
    int             (*pulse)(message_context_t *, int, unsigned, void *);
    int             (*parm)(input_module_t *, int, char *);
    int             (*devctrl)(input_module_t *, int, void *);
    int             (*shutdown)(input_module_t *, int);
};
flags
Only one flag has been defined -- MODULE_FLAG_INUSE, which indicates a valid module.
type
A combination (OR) of two descriptors:
args
List of module parameters where each parameter is represented by a single character. If there's an optional argument, the parameter has the format x: (the : means that the optional argument is expected).
data
Usually a pointer to a module's local data. This can be assigned in the init() module function.

In the sample directory

The code in the sample directory provides lots of comments detailing the steps required to initialize your module, and what to put in your module's functions.

You'll also find two modules:

You'll also find a README file that provides further background info on how the system processes data from keyboard and absolute devices.


Note: In many embedded systems, a combination device/protocol module is called for. For details, see the section on "Writing a combination device/protocol module" in this chapter.

Data format

Device modules can pass data in any format they want up to protocol modules. But protocol modules must pass data in a specific format to filter modules.

This protocol module: Must format data into a:
Keyboard struct packet_kbd
Relative struct packet_rel
Absolute struct packet_abs

See the header <devi.h> for the format of these structures. All these structures have a timestamp field; you fill them in using the library call clk_get().

Keyboard devices

When writing keyboard device modules, keep in mind that the protocol/filter layers will expect make-and-break scan codes indicating when a key is pressed down and released. The easiest thing to do is to map the scan codes your device sends to the standard PC scan codes. This way you won't have to make any filter-layer changes -- it will all just work like a normal PC keyboard. Standard PC scan codes are available in any PC hardware book.

When passing up a struct packet_kbd to the filter layer, all you need to do is:

  1. Fill in the key_scan field of the struct _keyboard_data with the scan code.
  2. Fill in the flags field with KEY_SCAN_VALID.

The keyboard filter layer will read in a keyboard definition file and interpret the scan codes it receives based on the contents of this file.

The keyboard definition files are typically kept in the location $PHOTON_PATH/keyboard, where $PHOTON_PATH depends on you system configuration (e.g. this might be /usr/photon on your machine). In this directory there's a file called sample.kdef, which provides a sample definition file. The .kdef files are compiled into .kbd files using the utilities kbcvt and mkkbd.


Note: Both of these utilities are shipped with Photon for QNX 4.

You shouldn't have to play around with these mapping files very much if you map your scan codes appropriately. The only place where you might need to modify these files is if your keyboard has special keys. In this case, you would start with a standard definition file (e.g. en_US_101.kdef), and add your unique scan codes.

When the driver starts up and initializes the keyboard filter module, the module will try to load in a mapping definition file. It uses the following algorithm to look for the file:

  1. The module tries to open the keyboard configuration file /etc/system/config/.KEYBOARD.hostname. If this file exists, the module just reads the keyboard filename from it.
  2. If the keyboard mapping filename is empty, the module tries to take it from the KBD environment variable.
  3. If the keyboard mapping filename is still empty, the module assigns the standard US keyboard definition file (en_US_101.kbd) to it.
  4. The module tries to find this file in the %PHOTON%/keyboard directory.
  5. If the PHOTON environment variable isn't defined, the module tries to open it in the /usr/photon/keyboard directory.

Absolute devices

The elo directory contains an example of a touchscreen protocol module.

Absolute devices (e.g. touchscreens) need to be calibrated. They typically generate "raw" coordinates that must be translated into actual screen coordinates. The screen coordinates they're translated into depend on the screen resolution.

The device/protocol layer module receives raw coordinates from the touchscreen device, formats a packet_abs structure, and passes it up to the absolute filter.

The absolute filter module takes care of translating raw coordinates into screen coordinates. To do this, the module tries to locate and read in a calibration file on startup via:

  1. Command-line option to the absolute filter (-f filename)
  2. ABSF environment variable
  3. /etc/system/config/calib.hostname

Calibration file format

The format of this file is as follows:

XLxYL:XHxYH:XRL XRH YRL YRH SWAP

where:

XL
X screen coordinate of upper left side (typically 0).
YL
Y screen coordinate of upper left side (typically 0).
XH
X screen coordinate of lower right side (typically X screen resolution - 1).
YH
Y screen coordinate of lower right side (typically Y screen resolution - 1).
XRL
Raw touchscreen X coordinate at upper left side.
XRH
Raw touchscreen X coordinate at lower right side.
YRL
Raw touchscreen Y coordinate at upper left side.
YRH
Raw touchscreen Y coordinate at lower right size.
SWAP
Whether to swap X or Y axes (0 is no, 1 is yes.) It's safe to leave this as 0.

This calibration file is typically generated by the Photon touchscreen calibration application, calib. When the utility starts, it sends a message to the devi- driver asking it to switch to raw mode, and then solicits coordinate info by asking the user to touch the screen at all four corners and the middle. After doing this, calib formats the absf file, sends a calibration message to the devi-* driver, and writes the file.

Relative devices

The hirun directory contains examples of a mouse device (kb.c) and protocol (msoft.c, ps2.s, msys.c) modules.

Since these modules cover all the main types of relative devices, you probably won't need to develop something new from scratch. If you need to implement support for any device that's not completely supported by this driver, you can simply copy the files from this directory into a new one and modify them.

Note that Microsoft and Mouse Systems class devices don't have a device module -- they just use /dev/serN to get raw data from a serial communication port. A PS/2 mouse shares the 8042 controller device driver (kb.c) with a standard keyboard.

The protocol layer module receives raw coordinates from the mouse, formats a packet_rel structure, and then passes it up to the relative filter.

The relative filter module implements an acceleration algorithm, converts raw data received from the protocol level according to the current speed parameter, and emits this data in the form of events to Photon.

Callbacks in your module

The main part of developing a new module involves implementing several standard callback functions, combined "under the roof" of the module's instance of the input_module_t structure.

Consider implementing the following callbacks:

init()
Should be called for a one-time initialization of a module's state after it's loaded.
reset()
Used to reset a module's and/or device's state. You would call it when the module is linked into an event bus line; if necessary, it could be called from your code as a reaction to any sort of device trouble.
input()
You usually implement this callback function in protocol modules as part of the device-to-interface data channel.
output()
Usually called by higher-layer modules asking for data to be sent to the device. You can use this callback for passing commands to control an input device.
pulse()
Usually implemented in device class modules. This callback is automatically activated each time that a registered interrupt handler wants to notify a device module about input activity.
parm()
Called by the Input Runtime System to parse any command-line parameters given to the module.
devctrl()
Used by modules in an event bus line to send commands to each other. This callback may also be called as a response to the external devctl() call. You can use this callback for reconfiguring a driver on the fly.
shutdown()
Called when the Input Runtime System is shutting down.

Which callbacks are required?

To decide which callback functions should be implemented in a module, you'll need to consider the module's purpose. In general, a device module must have the following functions:

A protocol module, in turn, must have at least the input() function (and optionally init(), parm(), and devctrl()).

Callback sequence

At startup, the Input Runtime System always calls a module's callback functions in the following sequence:

init() --> parm() --> reset()

Writing a combination device/protocol module

If you're writing a driver for a custom type of device where it doesn't make sense to split up the functionality of device and protocol, you can write a combination module.

To do this, you simply proceed as you would when writing a "normal" driver: fill in your callbacks, talk to your device, interpret its protocol, etc.

In addition, there are two things you have to do:

  1. In the type field, put in DEVI_MODULE_TYPE_DEVICE | DEVI_MODULE_TYPE_PROTO in addition to the DEVI_CLASS_ manifest.
  2. When you've interpreted the data from your device, package up a struct packet_* (depending on your class of device) and send it up.

A note about reentrancy

Because the devi-* framework is multithreaded, you should be aware of a possible reentrancy issue. When a devi-* driver is invoked, a module may be specified multiple times, where each invocation will belong to a separate event bus line.

An example is the keyboard controller device module (kb). This module can communicate with a keyboard and with a PS/2 mouse. We would invoke the driver as follows:

devi-hirun kbd kb ps2 kb -2

Here we'll have two event bus lines: one for the keyboard and one for the mouse. Upon initialization, the input framework will use the static kb data structure (input_module_t) for one of the bus lines and dynamically allocate/copy another one for the other bus line.

If you keep your module-specific data confined to the private data member of the module structure, you won't have any problems with reentrancy. But if your module contains global variables, then you'll have to use some sort of mutual exclusion mechanism for protection.

Note that you don't have to ensure that the init(), reset(), and parm() callbacks are reentrant, because they're always called from a single thread upon initialization. (However, if for some reason you need to call them when the runtime system is up, then you'd have to ensure that they're reentrant.) The callbacks used at runtime (e.g. the pulse() callback) are the ones at risk.

For more information, see the keyboard controller module code (hirun/kb.c).