Linux input device events, how to retrieve initial state

embedded, evdev, linux, linux-kernel

Solution

The evdev devices queue events until you `read()` them, so in most cases opening the device, doing the `ioctl()` and immediately starting to read events from it should work. If the driver dropped some events from the queue, it sends you a `SYN_DROPPED` event, so you can detect situations where that happened. The libevdev documentation has some ideas on how one should handle that situation; the way I read it you should simply retry, i.e. drop all pending events, and redo the `ioctl()` until there are no more `SYN_DROPPED` events.

I used this code to verify that this approach works:

#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/input.h>
#include <string.h>

#define EVDEV "/dev/input/event9"

int main(int argc, char **argv) {
    unsigned char key_states[KEY_MAX/8 + 1];
    struct input_event evt;
    int fd;

    memset(key_states, 0, sizeof(key_states));
    fd = open(EVDEV, O_RDWR);
    ioctl(fd, EVIOCGKEY(sizeof(key_states)), key_states);

    // Create some inconsistency
    printf("Type (lots) now to make evdev drop events from the queue\n");
    sleep(5);
    printf("\n");

    while(read(fd, &evt, sizeof(struct input_event)) > 0) {
        if(evt.type == EV_SYN && evt.code == SYN_DROPPED) {
            printf("Received SYN_DROPPED. Restart.\n");
            fsync(fd);
            ioctl(fd, EVIOCGKEY(sizeof(key_states)), key_states);
        }
        else if(evt.type == EV_KEY) {
            // Ignore repetitions
            if(evt.value > 1) continue;

            key_states[evt.code / 8] ^= 1 << (evt.code % 8);
            if((key_states[evt.code / 8] >> (evt.code % 8)) & 1 != evt.value) {
                printf("Inconsistency detected: Keycode %d is reported as %d, but %d is stored\n", evt.code, evt.value,
                        (key_states[evt.code / 8] >> (evt.code % 8)) & 1);
            }
        }
    }
}

After starting, the program deliberately waits 5 seconds. Hit some keys in that time to fill the buffer. On my system, I need to enter about 70 characters to trigger a `SYN_DROPPED`. The `EV_KEY` handling code checks if the events are consistent with the state reported by the `EVIOCGKEY` ioctl.

Problem

I am using the gpio-keys device driver to handle some buttons in an embedded device running Linux. Applications in user space can just open `/dev/input/eventX` and read input events in a loop. My question is how to get the initial states of the buttons. There is an ioctl call (`EVIOCGKEY`) which can be used for this, however if I first check this and then start to read from `/dev/input/eventX`, there's no way to guarantee that the state did not change in between. Any suggestions?

Original source