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.
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); };
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.
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. |
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().
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:
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.
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:
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:
The format of this file is as follows:
XLxYL:XHxYH:XRL XRH YRL YRH SWAP
where:
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.
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.
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:
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()).
At startup, the Input Runtime System always calls a module's callback functions in the following sequence:
init() → parm() → reset()
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:
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).