嵌入式linux中文站在线图书

Previous Page Next Page

Chapter 7. Input Drivers

In This Chapter

210
216
230
231


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:

  • Uniform handling of functionally similar input devices even when they are physically different. For example, all mice, such as PS/2, USB or Bluetooth, are treated alike.

  • An easy event interface for dispatching input reports to user applications. Your driver does not have to create and manage /dev nodes and related access methods. Instead, it can simply invoke input APIs to send mouse movements, key presses, or touch events upstream to user land. Applications such as X Windows work seamlessly over the event interfaces exported by the input subsystem.

  • Extraction of common portions out of input drivers and a resulting abstraction that simplifies the drivers and introduces consistency. For example, the input subsystem offers a collection of low-level drivers called serio that provides access to input hardware such as serial ports and keyboard controllers.

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)

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

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

Previous Page Next Page