Chapter 7. Input Drivers
http://www.embeddedlinux.org.cn/EssentialLinuxDeviceDrivers/final/ch07lev1sec1.html
Chapter 7. Input Drivers
In This Chapter |
||||||||
|
|
The kernel's input subsystem was created to unify scattered drivers that handle diverse classes of data-input devices such as keyboards, mice, trackballs, joysticks, roller wheels, touch screens, accelerometers, and tablets. The input subsystem brings the following advantages to the table:
Figure 7.1 illustrates the operation of the input subsystem. The subsystem contains two classes of drivers that work in tandem: event drivers and device drivers. Event drivers are responsible for interfacing with applications, whereas device drivers are responsible for low-level communication with input devices. The mouse event generator, mousedev, is an example of the former, and the PS/2 mouse driver is an example of the latter. Both event drivers and device drivers can avail the services of an efficient, bug-free, reusable core, which lies at the heart of the input subsystem. Figure 7.1. The input subsystem.
|
Because event drivers are standardized and available for all input classes, you are more likely to implement a device driver than an event driver. Your device driver can use a suitable existing event driver via the input core to interface with user applications. Note that this chapter uses the term device driver to refer to an input device driver as opposed to an input event driver.
Input Event Drivers
The event interfaces exported by the input subsystem have evolved into a standard that many graphical windowing systems understand. Event drivers offer a hardware-independent abstraction to talk to input devices, just as the frame buffer interface (discussed in Chapter 12, "Video Drivers") presents a generic mechanism to communicate with display devices. Event drivers, in tandem with frame buffer drivers, insulate graphical user interfaces (GUIs) from the vagaries of the underlying hardware.
The Evdev Interface
Evdev is a generic input event driver. Each event packet produced by evdev has the following format, defined in include/linux/input.h:
struct input_event {
struct timeval time; /* Timestamp */
__u16 type; /* Event Type */
__u16 code; /* Event Code */
__s32 value; /* Event Value */
};
To learn how to use evdev, let's implement an input device driver for a virtual mouse.
Device Example: Virtual Mouse
This is how our virtual mouse works: An application (coord.c) emulates mouse movements and dispatches coordinate information to the virtual mouse driver (vms.c) via a sysfs node, /sys/devices/platform/vms/coordinates. The virtual mouse driver (vms driver for short) channels these movements upstream via evdev. Figure 7.2 shows the details.
Figure 7.2. An input driver for a virtual mouse.
General-purpose mouse (gpm) is a server that lets you use a mouse in text mode without assistance from an X server. Gpm understands evdev messages, so the vms driver can directly communicate with it. After you have everything in place, you can see the cursor dancing over your screen to the tune of the virtual mouse movements streamed by coord.c.
Listing 7.1 contains coord.c, which continuously generates random X and Y coordinates. Mice, unlike joysticks or touch screens, produce relative coordinates, so that is what coord.c does. The vms driver is shown in Listing 7.2.
Listing 7.1. Application to Simulate Mouse Movements (coord.c)
|
Code View:
#include
int
main(int argc, char *argv[])
{
int sim_fd;
int x, y;
char buffer[10];
/* Open the sysfs coordinate node */
sim_fd = open("/sys/devices/platform/vms/coordinates", O_RDWR);
if (sim_fd < 0) {
perror("Couldn't open vms coordinate file\n");
exit(-1);
}
while (1) {
/* Generate random relative coordinates */
x = random()%20;
y = random()%20;
if (x%2) x = -x; if (y%2) y = -y;
/* Convey simulated coordinates to the virtual mouse driver */
sprintf(buffer, "%d %d %d", x, y, 0);
write(sim_fd, buffer, strlen(buffer));
fsync(sim_fd);
sleep(1);
}
close(sim_fd);
}
|
Listing 7.2. Input Driver for the Virtual Mouse (vms.c)
|
Code View:
#include
#include
#include
#include
#include
struct input_dev *vms_input_dev; /* Representation of an input device */
static struct platform_device *vms_dev; /* Device structure */
/* Sysfs method to input simulated
coordinates to the virtual
mouse driver */
static ssize_t
write_vms(struct device *dev,
struct device_attribute *attr,
const char *buffer, size_t count)
{
int x,y;
sscanf(buffer, "%d%d", &x, &y);
/* Report relative coordinates via the
event interface */
input_report_rel(vms_input_dev, REL_X, x);
input_report_rel(vms_input_dev, REL_Y, y);
input_sync(vms_input_dev);
return count;
}
/* Attach the sysfs write method */
DEVICE_ATTR(coordinates, 0644, NULL, write_vms);
/* Attribute Descriptor */
static struct attribute *vms_attrs[] = {
&dev_attr_coordinates.attr,
NULL
};
/* Attribute group */
static struct attribute_group vms_attr_group = {
.attrs = vms_attrs,
};
/* Driver Initialization */
int __init
vms_init(void)
{
/* Register a platform device */
vms_dev = platform_device_register_simple("vms", -1, NULL, 0);
if (IS_ERR(vms_dev)) {
PTR_ERR(vms_dev);
printk("vms_init: error\n");
}
/* Create a sysfs node to read simulated coordinates */
sysfs_create_group(&vms_dev->dev.kobj, &vms_attr_group);
/* Allocate an input device data structure */
vms_input_dev = input_allocate_device();
if (!vms_input_dev) {
printk("Bad input_alloc_device()\n");
}
/* Announce that the virtual mouse will generate
relative coordinates */
set_bit(EV_REL, vms_input_dev->evbit);
set_bit(REL_X, vms_input_dev->relbit);
set_bit(REL_Y, vms_input_dev->relbit);
/* Register with the input subsystem */
input_register_device(vms_input_dev);
printk("Virtual Mouse Driver Initialized.\n");
return 0;
}
/* Driver Exit */
void
vms_cleanup(void)
{
/* Unregister from the input subsystem */
input_unregister_device(vms_input_dev);
/* Cleanup sysfs node */
sysfs_remove_group(&vms_dev->dev.kobj, &vms_attr_group);
/* Unregister driver */
platform_device_unregister(vms_dev);
return;
}
module_init(vms_init);
module_exit(vms_cleanup);
|
Let's take a closer look at Listing 7.2. During initialization, the vms driver registers itself as an input device driver. For this, it first allocates an input_dev structure using the core API, input_allocate_device():
vms_input_dev = input_allocate_device();
It then announces that the virtual mouse generates relative events:
set_bit(EV_REL, vms_input_dev->evbit); /* Event Type is EV_REL */
Next, it declares the event codes that the virtual mouse produces:
set_bit(REL_X, vms_input_dev->relbit); /* Relative 'X' movement */ set_bit(REL_Y, vms_input_dev->relbit); /* Relative 'Y' movement */
If your virtual mouse is also capable of generating button clicks, you need to add this to vms_init():
set_bit(EV_KEY, vms_input_dev->evbit); /* Event Type is EV_KEY */ set_bit(BTN_0, vms_input_dev->keybit); /* Event Code is BTN_0 */
Finally, the registration:
input_register_device(vms_input_dev);
write_vms() is the sysfs store() method that attaches to /sys/devices/platform/vms/coordinates. When coord.c writes an X/Y pair to this file, write_vms() does the following:
input_report_rel(vms_input_dev, REL_X, x); input_report_rel(vms_input_dev, REL_Y, y); input_sync(vms_input_dev);
The first statement generates a REL_X event or a relative device movement in the X direction. The second produces a REL_Y event or a relative movement in the Y direction. input_sync() indicates that this event is complete, so the input subsystem collects these two events into a single evdev packet and sends it out of the door through /dev/input/eventX, where X is the interface number assigned to the vms driver. An application reading this file will receive event packets in the input_event format described earlier. To request gpm to attach to this event interface and accordingly chase the cursor around your screen, do this:
bash> gpm -m /dev/input/eventX -t evdev
The ADS7846 touch controller driver and the accelerometer driver, discussed respectively under the sections "Touch Controllers" and "Accelerometers" later, are also evdev users.
More Event Interfaces
The vms driver utilizes the generic evdev event interface, but input devices such as keyboards, mice, and touch controllers have custom event drivers. We will look at them when we discuss the corresponding device drivers.
To write your own event driver and export it to user space via /dev/input/mydev, you have to populate a structure called input_handler and register it with the input core as follows:
static struct input_handler my_event_handler = {
.event = mydev_event, /* Handle event reports sent by
input device drivers that use
this event driver's services */
.fops = &mydev_fops, /* Methods to manage
/dev/input/mydev */
.minor = MYDEV_MINOR_BASE, /* Minor number of
/dev/input/mydev */
.name = "mydev", /* Event driver name */
.id_table = mydev_ids, /* This event driver can handle
requests from these IDs */
.connect = mydev_connect, /* Invoked if there is an
ID match */
.disconnect = mydev_disconnect, /* Called when the driver unregisters
*/
};
/* Driver Initialization */
static int __init
mydev_init(void)
{
/* ... */
input_register_handler(&my_event_handler);
/* ... */
return 0;
}
Look at the implementation of mousedev (drivers/input/mousedev.c) for a complete example.

浙公网安备 33010602011771号