嵌入式linux中文站在线图书

Previous Page Next Page

Input Device Drivers

Let's turn our attention to drivers for common input devices such as keyboards, mice, and touch screens. But first, let's take a quick look at an off-the-shelf hardware access facility available to input drivers.

Serio

The serio layer offers library routines to access legacy input hardware such as i8042-compatible keyboard controllers and the serial port. PS/2 keyboards and mice interface with the former, whereas serial touch controllers connect to the latter. To communicate with hardware serviced by serio, for example, to send a command to a PS/2 mouse, register prescribed callback routines with serio using serio_register_driver().

To add a new driver as part of serio, register open()/close()/start()/stop()/write() entry points using serio_register_port (). Look at drivers/input/serio/serport.c for an example.

As you can see in Figure 7.1, serio is only one route to access low-level hardware. Several input device drivers instead rely on low-level support from bus layers such as USB or SPI.

Keyboards

Keyboards come in different flavors—legacy PS/2, USB, Bluetooth, Infrared, and so on. Each type has a specific input device driver, but all use the same keyboard event driver, thus ensuring a consistent interface to their users. The keyboard event driver, however, has a distinguishing feature compared to other event drivers: It passes data to another kernel subsystem (the tty layer), rather than to user space via /dev nodes.

PC Keyboards

The PC keyboard (also called PS/2 keyboard or AT keyboard) interfaces with the processor via an i8042-compatible keyboard controller. Desktops usually have a dedicated keyboard controller, but on laptops, keyboard interfacing is one of the responsibilities of a general-purpose embedded controller (see the section "Embedded Controllers" in Chapter 20, "More Devices and Drivers"). When you press a key on a PC keyboard, this is the road it takes:

  1. The keyboard controller (or the embedded controller) scans and decodes the keyboard matrix and takes care of nuances such as key debouncing.

  2. The keyboard device driver, with the help of serio, reads raw scancodes from the keyboard controller for each key press and release. The difference between a press and a release is in the most significant bit, which is set for the latter. A push on the "a" key, for example, yields a pair of scancodes, 0x1e and 0x9e. Special keys are escaped using 0xE0, so a jab on the right-arrow key produces the sequence, (0xE0 0x4D 0xE0 0xCD). You may use the showkey utility to observe scancodes emanating from the controller (the rightwards double arrowsymbol attaches explanations):

    bash> showkey -s
    kb mode was UNICODE
    [ if you are trying this under X, it might not work since
     the X server is also reading /dev/console ]
    
     press any key (program terminates 10s after last
     keypress)...
     ...
     0x1e 0x9e rightwards double arrow A push of the "a" key
  3. The keyboard device driver converts received scancodes to keycodes, based on the input mode. To see the keycode corresponding to the "a" key:

    bash> showkey
    ...
    keycode 30 press   rightwards double arrow A push of the "a" key
    keycode 30 release rightwards double arrow Release of the "a" key

    To report the keycode upstream, the driver generates an input event, which passes control to the keyboard event driver.

  4. The keyboard event driver undertakes keycode translation depending on the loaded key map. (See man pages of loadkeys and the map files present in /lib/kbd/keymaps.) It checks whether the translated keycode is tied to actions such as switching the virtual console or rebooting the system. To glow the CAPSLOCK and NUMLOCK LEDs instead of rebooting the system in response to a Ctrl+Alt+Del push, add the following to the Ctrl+Alt+Del handler of the keyboard event driver, drivers/char/keyboard.c:

    static void fn_boot_it(struct vc_data *vc,
    
                           struct pt_regs *regs)
    {
    +  set_vc_kbd_led(kbd, VC_CAPSLOCK);
    +  set_vc_kbd_led(kbd, VC_NUMLOCK);
    -  ctrl_alt_del();
    }
  5. For regular keys, the translated keycode is sent to the associated virtual terminal and the N_TTY line discipline. (We discussed virtual terminals and line disciplines in Chapter 6, "Serial Drivers.") This is done as follows by drivers/char/keyboard.c:

    /* Add the keycode to flip buffer */
    tty_insert_flip_char(tty, keycode, 0);
    /* Schedule */
    con_schedule_flip(tty);

The N_TTY line discipline processes the input thus received from the keyboard, echoes it to the virtual console, and lets user-space applications read characters from the /dev/ttyX node connected to the virtual terminal.

Figure 7.3 shows the data flow from the time you push a key on your keyboard until the time it's echoed on your virtual console. The left half of the figure is hardware-specific, and the right half is generic. As per the design goal of the input subsystem, the underlying hardware interface is transparent to the keyboard event driver and the tty layer. The input core and the clearly defined event interfaces thus insulate input users from the intricacies of the hardware.

Figure 7.3. Data flow from a PS/2-compatible keyboard.


USB and Bluetooth Keyboards

The USB specifications related to human interface devices (HID) stipulate the protocol that USB keyboards, mice, keypads, and other input peripherals use for communication. On Linux, this is implemented via the usbhid USB client driver, which is responsible for the USB HID class (0x03). Usbhid registers itself as an input device driver. It conforms to the input API and reports input events appropriate to the connected HID.

To understand the code path for a USB keyboard, revert to Figure 7.3 and modify the hardware-specific left half. Replace the keyboard controller in the Input Hardware box with a USB controller, serio with the USB core layer, and the Input Device Driver box with the usbhid driver.

For a Bluetooth keyboard, replace the keyboard controller in Figure 7.3 with a Bluetooth chipset, serio with the Bluetooth core layer, and the Input Device Driver box with the Bluetooth hidp driver.

USB and Bluetooth are discussed in detail in Chapter 11, "Universal Serial Bus," and Chapter 16, "Linux Without Wires," respectively.

Mice

Mice, like keyboards, come with different capabilities and have different interfacing options. Let's look at the common ones.

PS/2 Mice

Mice generate relative movements in the X and Y axes. They also possess one or more buttons. Some have scroll wheels, too. The input device driver for PS/2-compatible legacy mice relies on the serio layer to talk to the underlying controller. The input event driver for mice, called mousedev, reports mouse events to user applications via /dev/input/mice.

Device Example: Roller Mouse

To get a feel of a real-world mouse device driver, let's convert the roller wheel discussed in Chapter 4, "Laying the Groundwork," into a variation of the generic PS/2 mouse. The "roller mouse" generates one-dimensional movement in the Y-axis. Clockwise and anticlockwise turns of the wheel produce positive and negative relative Y coordinates respectively (like the scroll wheel in mice), while pressing the roller wheel results in a left button mouse event. The roller mouse is thus ideal for navigating menus in devices such as smart phones, handhelds, and music players.

The roller mouse device driver implemented in Listing 7.3 works with windowing systems such as X Windows. Look at roller_mouse_init() to see how the driver declares its mouse-like capabilities. Unlike the roller wheel driver in Listing 4.1 of Chapter 4, the roller mouse driver needs no read() or poll() methods because events are reported using input APIs. The roller interrupt handler roller_isr() also changes accordingly. Gone are the housekeepings done in the interrupt handler using a wait queue, a spinlock, and the store_movement() routine to support read() and poll().

In Listing 7.3, the leading + and - denote the differences from the roller wheel driver implemented in Listing 4.1 of Chapter 4.

Listing 7.3. The Roller Mouse Driver

+  #include 
+  #include 

+  /* Device structure */
+  struct {
+    /* ... */
+    struct input_dev dev;
+  } roller_mouse;

+  static int __init
+  roller_mouse_init(void)
+  {
+  /* Allocate input device structure */
+  roller_mouse->dev = input_allocate_device();
+
+  /* Can generate a click and a relative movement */
+  roller_mouse->dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REL);

+  /* Can move only in the Y-axis */
+  roller_mouse->dev->relbit[0] = BIT(REL_Y);
+
+  /* My click should be construed as the left button
+     press of a mouse */
+  roller_mouse->dev->keybit[LONG(BTN_MOUSE)] = BIT(BTN_LEFT);

+  roller_mouse->dev->name = "roll";
+
+  /* For entries in /sys/class/input/inputX/id/ */
+  roller_mouse->dev->id.bustype = ROLLER_BUS;
+  roller_mouse->dev->id.vendor = ROLLER_VENDOR;
+  roller_mouse->dev->id.product = ROLLER_PROD;
+  roller_mouse->dev->id.version = ROLLER_VER;

+  /* Register with the input subsystem */
+  input_register_device(roller_mouse->dev);
+}

/* Global variables */
- spinlock_t roller_lock = SPIN_LOCK_UNLOCKED;
- static DECLARE_WAIT_QUEUE_HEAD(roller_poll);

/* The Roller Interrupt Handler */
static irqreturn_t
roller_interrupt(int irq, void *dev_id)
{
  int i, PA_t, PA_delta_t, movement = 0;

  /* Get the waveforms from bits 0, 1 and 2
     of Port D as shown in Figure 7.1 */
  PA_t = PORTD & 0x07;

  /* Wait until the state of the pins change.
     (Add some timeout to the loop) */
  for (i=0; (PA_t==PA_delta_t); i++){
     PA_delta_t = PORTD & 0x07;
  }

  movement = determine_movement(PA_t, PA_delta_t);

- spin_lock(&roller_lock);
-
- /* Store the wheel movement in a buffer for
-    later access by the read()/poll() entry points */
- store_movements(movement);
-
- spin_unlock(&roller_lock);
-
- /* Wake up the poll entry point that might have
-    gone to sleep, waiting for a wheel movement */
- wake_up_interruptible(&roller_poll);
-
+ if (movement == CLOCKWISE) {
+   input_report_rel(roller_mouse->dev, REL_Y, 1);
+ } else if (movement == ANTICLOCKWISE) {
+   input_report_rel(roller_mouse->dev, REL_Y, -1);
+ } else if (movement == KEYPRESSED) {
+   input_report_key(roller_mouse->dev, BTN_LEFT, 1);
+ }
+ input_sync(roller_mouse->dev);

  return IRQ_HANDLED;
}

					  

 

Trackpoints

A trackpoint is a pointing device that comes integrated with the PS/2-type keyboard on several laptops. This device includes a joystick located among the keys and mouse buttons positioned under the spacebar. A trackpoint essentially functions as a mouse, so you can operate it using the PS/2 mouse driver.

Unlike a regular mouse, a trackpoint offers more movement control. You can command the trackpoint controller to change properties such as sensitivity and inertia. The kernel has a special driver, drivers/input/mouse/trackpoint.c, to create and manage associated sysfs nodes. For the full set of track point configuration options, look under /sys/devices/platform/i8042/serioX/serioY/.

Touchpads

A touchpad is a mouse-like pointing device commonly found on laptops. Unlike conventional mice, a touchpad does not have moving parts. It can generate mouse-compatible relative coordinates but is usually used by operating systems in a more powerful mode that produces absolute coordinates. The communication protocol used in absolute mode is similar to the PS/2 mouse protocol, but not compatible with it.

The basic PS/2 mouse driver is capable of supporting devices that conform to different variations of the bare PS/2 mouse protocol. You may add support for a new mouse protocol to the base driver by supplying a protocol driver via the psmouse structure. If your laptop uses the Synaptics touchpad in absolute mode, for example, the base PS/2 mouse driver uses the services of a Synaptics protocol driver to interpret the streaming data. For an end-to-end understanding of how the Synaptics protocol works in tandem with the base PS/2 driver, look at the following four code regions collected in Listing 7.4:

Listing 7.4. PS/2 Mouse Protocol Driver for the Synaptics Touchpad

drivers/input/mouse/psmouse-base.c:
/* List of supported PS/2 mouse protocols */
static struct psmouse_protocol psmouse_protocols[] = {
 {
   .type     = PSMOUSE_PS2, /* The bare PS/2 handler */
   .name     = "PS/2",
   .alias    = "bare",
   .maxproto = 1,
   .detect   = ps2bare_detect,
 },
 /*  ... */
 {
   .type    = PSMOUSE_SYNAPTICS, /* Synaptics TouchPad Protocol */
   .name    = "SynPS/2",
   .alias   = "synaptics",
   .detect  = synaptics_detect,  /* Is the protocol detected? */
   .init    = synaptics_init,    /* Initialize Protocol Handler */
 },
  /*  ... */
}


drivers/input/mouse/psmouse.h:
/* The structure that ties various mouse protocols together */
struct psmouse {
  struct input_dev *dev; /* The input device */
  /* ... */

  /* Protocol Methods */
  psmouse_ret_t (*protocol_handler)
                 (struct psmouse *psmouse, struct pt_regs *regs);
  void (*set_rate)(struct psmouse *psmouse, unsigned int rate);
  void (*set_resolution)
        (struct psmouse *psmouse, unsigned int resolution);
  int (*reconnect)(struct psmouse *psmouse);
  void (*disconnect)(struct psmouse *psmouse);
  /* ... */
};

drivers/input/mouse/synaptics.c:
/* init() method of the Synaptics protocol */
int synaptics_init(struct psmouse *psmouse)
{
  struct synaptics_data *priv;
  psmouse->private = priv = kmalloc(sizeof(struct synaptics_data),
                                    GFP_KERNEL);
  /* ... */

  /* This is called in interrupt context when mouse
     movement is sensed */
  psmouse->protocol_handler = synaptics_process_byte;

  /* More protocol methods */
  psmouse->set_rate = synaptics_set_rate;
  psmouse->disconnect = synaptics_disconnect;
  psmouse->reconnect = synaptics_reconnect;

  /* ... */
}

drivers/input/mouse/synaptics.c:
/* If you unfold synaptics_process_byte() and look at
   synaptics_process_packet(), you can see the input
   events being reported to user applications via mousedev */
static void synaptics_process_packet(struct psmouse *psmouse)
{
  /* ... */
  if (hw.z > 0) {
    /* Absolute X Coordinate */
    input_report_abs(dev, ABS_X, hw.x);
    /* Absolute Y Coordinate */
    input_report_abs(dev, ABS_Y,
                     YMAX_NOMINAL + YMIN_NOMINAL - hw.y);
  }
  /* Absolute Z Coordinate */
  input_report_abs(dev, ABS_PRESSURE, hw.z);
  /* ... */
  /* Left TouchPad button */
  input_report_key(dev, BTN_LEFT, hw.left);
  /* Right TouchPad button */
  input_report_key(dev, BTN_RIGHT, hw.right);
  /* ... */
}

					  

 

USB and Bluetooth Mice

USB mice are handled by the same input driver (usbhid) that drives USB keyboards. Similarly, the hidp driver that implements support for Bluetooth keyboards also takes care of Bluetooth mice.

As you would expect, USB and Bluetooth mice drivers channel device data through mousedev.

Touch Controllers

In Chapter 6, we implemented a device driver for a serial touch controller in the form of a line discipline called N_TCH. The input subsystem offers a better and easier way to implement that driver. Refashion the finite state machine in N_TCH as an input device driver with the following changes:

  1. Serio offers a line discipline called serport for accessing devices connected to the serial port. Use serport's services to talk to the touch controller.

  2. Instead of passing coordinate information to the tty layer, generate input reports via evdev as you did in Listing 7.2 for the virtual mouse.

With this, the touch screen is accessible to user space via /dev/input/eventX. The actual driver implementation is left as an exercise.

An example of a touch controller that does not interface via the serial port is the Analog Devices ADS7846 chip, which communicates over a Serial Peripheral Interface (SPI). The driver for this device uses the services of the SPI core rather than serio. The section "The Serial Peripheral Interface Bus" in Chapter 8, "The Inter-Integrated Circuit Protocol," discusses SPI. Like most touch drivers, the ADS7846 driver uses the evdev interface to dispatch touch information to user applications.

Some touch controllers interface over USB. An example is the 3M USB touch controller, driven by drivers/input/touchscreen/usbtouchscreen.c.

Many PDAs have four-wire resistive touch panels superimposed on their LCDs. The X and Y plates of the panel (two wires for either axes) connect to an analog-to-digital converter (ADC), which provides a digital readout of the analog voltage difference arising out of touching the screen. An input driver collects the coordinates from the ADC and dispatches it to user space.


Different instances of the same touch panel may produce slightly different coordinate ranges (maximum values in the X and Y directions) due to the nuances of manufacturing processes. To insulate applications from this variation, touch screens are calibrated prior to use. Calibration is usually initiated by the GUI by displaying cross-marks at screen boundaries and other vantage points, and requesting the user to touch those points. The generated coordinates are programmed back into the touch controller using appropriate commands if it supports self-calibration, or used to scale the coordinate stream in software otherwise.

The input subsystem also contains an event driver called tsdev that generates coordinate information according to the Compaq touch-screen protocol. If your system reports touch events via tsdev, applications that understand this protocol can elicit touch input from /dev/input/tsX. This driver is, however, scheduled for removal from the mainline kernel in favor of the user space tslib library. Documentation/feature-removal-schedule.txt lists features that are going away from the kernel source tree.

Accelerometers

An accelerometer measures acceleration. Several IBM/Lenovo laptops have an accelerometer that detects sudden movement. The generated information is used to protect the hard disk from damage using a mechanism called Hard Drive Active Protection System (HDAPS), analogous to the way a car airbag shields a passenger from injury. The HDAPS driver is implemented as a platform driver that registers with the input subsystem. It uses evdev to stream the X and Y components of the detected acceleration. Applications can read acceleration events via /dev/input/eventX to detect conditions, such as shock and vibe, and perform a defensive action, such as parking the hard drive's head. The following command spews output if you move the laptop (assume that event3 is assigned to HDAPS):

bash> od –x /dev/input/event3
0000000 a94d 4599 1f19 0007 0003 0000 ffed ffff
...

The accelerometer also provides information such as temperature, keyboard activity, and mouse activity, all of which can be gleaned via files in /sys/devices/platform/hdaps/. Because of this, the HDAPS driver is part of the hardware monitoring (hwmon) subsystem in the kernel sources. We talk about hardware monitoring in the section "Hardware Monitoring with LM-Sensors" in the next chapter.

Output Events

Some input device drivers also handle output events. For example, the keyboard driver can glow the CAPSLOCK LED, and the PC speaker driver can sound a beep. Let's zoom in on the latter. During initialization, the speaker driver declares its output capability by setting appropriate evbits and registering a callback routine to handle the output event:

drivers/input/misc/pcspkr.c:
static int __devinit pcspkr_probe(struct platform_device *dev)
{
  /* ... */

  /* Capability Bits */
  pcspkr_dev->evbit[0]  = BIT(EV_SND);
  pcspkr_dev->sndbit[0] = BIT(SND_BELL) | BIT(SND_TONE);

  /* The Callback routine */
  pcspkr_dev->event = pcspkr_event;

  err = input_register_device(pcspkr_dev);
  /* ... */
}

/* The callback routine */
static int pcspkr_event(struct input_dev *dev, unsigned int type,
                        unsigned int code, int value)
{

  /* ... */

  /* I/O programming to sound a beep */

  outb_p(inb_p(0x61) | 3, 0x61);
  /* set command for counter 2, 2 byte write */
  outb_p(0xB6, 0x43);
  /* select desired HZ */
  outb_p(count & 0xff, 0x42);
  outb((count >> 8) & 0xff, 0x42);

  /* ... */
}

					  

To sound the beeper, the keyboard event driver generates a sound event (EV_SND) as follows:

input_event(handle->dev, EV_SND,    /* Type */
                         SND_TONE,  /* Code */
                         hz         /* Value */);

This triggers execution of the callback routine, pcspkr_event(), and you hear the beep.

Previous Page Next Page