How to implement an interrupt driven GPIO input in Linux

With Linux, some of the things that seem like they should be easy are not — at least at first glance.  For example, how do you read an interrupt driven GPIO input in a Linux application?  With simpler microcontroller systems, this is straightforward, but with a system like Linux, you have to navigate through several layers of software (and for very good reasons).  You can’t handle interrupts directly in a Linux application, so this means you need a kernel component involved.  This operation of reading a GPIO resembles a key press, so the Linux input subsystem might be a good place to start looking.  Once we take that route, we discover the gpio_keys driver.

Configuring the gpio_keys driver

The gpio_keys driver is configured with a few lines of code in your Linux board configuration file.  An example is below:

static struct gpio_keys_button svs_button_table[] = {
  { .code = KEY_RECORD,
    .gpio = PP_GPIO_MIC_EN,
    .active_low = 1,
    .desc = "MIC_EN",
    .type = EV_KEY,
    .wakeup = 0,
  },
};

In this application, we are reading button presses on a cell phone style headset.  Once the gpio_keys driver is configured, a new entry will show up in /dev/input/eventX.  An application can then do a blocking read on this device.

Reading the GPIO in an application

To read the GPIO, we simply do a blocking read on the new /dev/input/eventX device.  The read will block until there is a change in GPIO state.  An example is show below:

#define MIC_INPUT_DEV  "/dev/input/event0"

static gboolean mic_button_callback(GIOChannel *source, GIOCondition condition, gpointer data)
{
  struct input_event ev;
  int bytes_read;

  g_io_channel_read_chars(source, (gchar *)&ev, sizeof(ev), &bytes_read, NULL);

  if (bytes_read > 0) {
    if (bytes_read != sizeof(ev)) {
      s_debug(1, "warning, only read %i bytes from mic input");
      return TRUE;
    }
  } else {
    return TRUE;
  }

  if (ev.type != EV_SYN && ev.value == 1) {
    /* button pressed, do something ... */
  }

  return TRUE;
}

void mic_button_init()
{
  GIOChannel * micbutton = g_io_channel_new_file(MIC_INPUT_DEV, "r", NULL);

  if (micbutton == NULL) {
    s_debug(TRUE, "Error initializing mic button");
    return;
  }

  g_io_channel_set_encoding(micbutton, NULL, NULL);

  guint id = g_io_add_watch(micbutton, G_IO_IN, mic_button_callback, NULL);
}

The above example also shows how to incorporate the GPIO read into a GLib mainloop so that you don’t need to create a separate thread. ( As a side, GLib mainloop programming is worth learning!)  Using this method, reading a GPIO interrupt is easy and requires very few lines of code.  This is typical of complex systems like Linux — if you know how to do something, it is relatively easy, but getting started down the right path is sometimes the challenge.

8 replies on “How to implement an interrupt driven GPIO input in Linux”

  1. I try this code by capturing /dev/input/event1 with the below mentioned code by for some reason i havent been able to figure out i end up getting “Error initializing mic button” . Any clues why this might be happening ?

    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #define MIC_INPUT_DEV “/dev/input/event1”

    static gboolean mic_button_callback(GIOChannel *source, GIOCondition condition, gpointer data)
    {
    struct input_event ev;
    int bytes_read;

    g_io_channel_read_chars(source, (gchar *)&ev, sizeof(ev), &bytes_read, NULL);

    if (bytes_read > 0) {
    if (bytes_read != sizeof(ev)) {
    // s_debug(1, “warning, only read %i bytes from mic input”);
    printf(“warning, only read %i bytes from mic input”);
    return TRUE;
    }
    } else {
    return TRUE;
    }

    if (ev.type != EV_SYN && ev.value == 1) {
    /* button pressed, do something … */
    printf(“Power Key pressed YIPPY YIPPY”);
    }

    return TRUE;
    }

    void mic_button_init()
    //void main()
    {
    GIOChannel * micbutton = g_io_channel_new_file(MIC_INPUT_DEV, “r”, NULL);

    if (micbutton == NULL) {
    // s_debug(TRUE, “Error initializing mic button”);
    printf(“Error initializing mic button”);
    return;
    }

    g_io_channel_set_encoding(micbutton, NULL, NULL);

    guint id = g_io_add_watch(micbutton, G_IO_IN, mic_button_callback, NULL);
    }
    void main()
    {
    mic_button_init();
    }

  2. Had to do a sudo inorder for the application to have sufficient permissions to access the event node. Thanks Cliff !

  3. Can you please tell me how to cross compile that application. What are the header files and where can i find them. I am using openembedded to build kernel, on ubuntu. Thank you

  4. cliff thanks for nice post.
    Can you please tell me, if device is present in sys-fs instead of /dev/input then also callbacks are valid.
    I mean
    #define MIC_INPUT_DEV “/sys/devices/platfrom/pio0.4”

    Thanks,

  5. Hi,

    I’m using this technique to trap a change on a GPIO line. The event is working and I do receive it when a change happens on the gpio. The thing is, I would like to be able to read the value of that GPIO when my program start. If it’s in a certain state, I would like to react right away. When I try to export the pin with echo > “xxx” /sys/class/gpio/export it isn’t created. Is it a limitation where I can’t use a gpio that is being used as an interrupt source at the same time as a gpio that I can poll?

    Thanks!

Comments are closed.