1

I want to use the GPIO pins to wait for a button-press without using a CPU spin loop. My preferred way of using the GPIO pins is via the sysfs interface at /sys/class/gpio, but it seems to me that there is an inherent race condition in doing so. Namely, if I understand the sysfs interface to GPIO correctly, it seems one must go through the following sequence:

  1. Read the value file to see whether the desired condition holds true.
  2. If it does not (the usual case for the first iteration), poll the value file for POLLPRI to sleep until it changes state, and repeat from step 1.

However, in this procedure, there is a window of opportunity between steps 1 and 2 such that the button is not yet pressed when the value is read, but then pushed right before entering doing the poll call, in which case this particular button press would effectively be missed.

I mean, I realize that a low-frequency event like button presses doesn't really have a high probability of triggering this race condition, but there are certainly more high-frequency events that could, and even regardless of that, it just seems ugly. Is there a way to avoid this problem?

11
  • Not to try to talk you out of your preference for sysfs, but it's been deprecated and may not be around much longer.
    – Seamus
    Commented Sep 15, 2018 at 20:15
  • @Seamus: I'm fine with being talked out of sysfs access, I just don't quite know what the alternative would be, except direct register access, which seems a bit ugly in comparison to using user-space interfaces.
    – Dolda2000
    Commented Sep 15, 2018 at 20:38
  • Follow the first link in my comment above - there are at least two good libraries, and that link discusses them. pigpio is one of those libraries, and I see @joan has already answered, so you're in good hands.
    – Seamus
    Commented Sep 15, 2018 at 20:43
  • I think the real issue here is the implication that you have to wait to start polling after a point when the button press could occur, which implies a significant design flaw.
    – goldilocks
    Commented Nov 25, 2021 at 15:54
  • @goldilocks: Not sure what you're talking about. There is always a particular point in time where the process enters the poll call, and my question is about how to coordinate that with a point when I know that the button is not already pressed.
    – Dolda2000
    Commented Nov 27, 2021 at 19:58

2 Answers 2

3

You should probably think about migrating to the new gpiochip interface for any new software. One improvement is that it gives you the likely time of GPIO level changes.

I have done pretty much what you describe with sysfs without noticing a problem. I suspect that once you have set the GPIO as an input and given an edge the system is primed to respond to level changes. That may be why I consume any prior interrupt in this code.

I suggest having a look in wiringPi or (my) pigpio which both have sysfs interrupt code.

/* 2014-07-06
   wfi.c

   gcc -o wfi wfi.c

   ./wfi [gpio]
*/

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/time.h>
#include <poll.h>

#define GPIO 4

int main(int argc, char *argv[])
{
   char str[256];
   struct timeval tv;
   struct pollfd pfd;
   int fd, gpio;
   char buf[8];

   /*
      Prior calls assumed.
      sudo sh -c "echo 4      >/sys/class/gpio/export"
      sudo sh -c "echo in     >/sys/class/gpio/gpio4/direction"
      sudo sh -c "echo rising >/sys/class/gpio/gpio4/edge"
   */

   if (argc > 1) gpio = atoi(argv[1]);
   else          gpio = GPIO;

   sprintf(str, "/sys/class/gpio/gpio%d/value", gpio);

   if ((fd = open(str, O_RDONLY)) < 0)
   {
      fprintf(stderr, "Failed, gpio %d not exported.\n", gpio);
      exit(1);
   }

   pfd.fd = fd;

   pfd.events = POLLPRI;

   lseek(fd, 0, SEEK_SET);    /* consume any prior interrupt */
   read(fd, buf, sizeof buf);

   poll(&pfd, 1, -1);         /* wait for interrupt */

   lseek(fd, 0, SEEK_SET);    /* consume interrupt */
   read(fd, buf, sizeof buf);

   exit(0);
}

I didn't set up the edge and direction within the above code because I wanted the program to work without running with root permissions.

3
  • "The new gpiochip interface", you say? Does this refer to the /dev/gpiochip* files? Are they preferable to the sysfs interface? I've been trying to find documentation about it, but have been unsuccessful.
    – Dolda2000
    Commented Sep 15, 2018 at 20:41
  • Have a search for a user space interface called libgpiod. E.g. cnx-software.com/2017/11/03/…
    – joan
    Commented Sep 15, 2018 at 20:52
  • i tried do it in python, polling on 1 works as expected, but polling then 0 fires every 0.00007 seconds
    – eri
    Commented Mar 16, 2022 at 11:52
1

Yes, there is a simple bash way for GPIO interrupt controlled read via sysfs interface:

echo 4 > /sys/class/gpio/export
echo in > /sys/class/gpio/gpio4/direction
echo both > /sys/class/gpio/gpio4/edge

and now the blocking wait for change interrupt for the resp. value (inotifywait will block until the value will change, e.g. falling or raising, but this will work only, if the edge has been set).

inotifywait -e modify /sys/class/gpio/gpio4/value
# here you may start your handling for a change of value

you may change the edge from both to falling or raising only, whatever you want. You can write a blocking watchdog script for every GPIO you want to watch with the lines above in an endless loop. Have fun. The gpio setting part above must be run by root, but only once at startup or any other suitable place. The inotify loop below could be run by any user.

2
  • 1
    Correct me if I'm wrong, but I'm not seeing how this saves the race condition that the question asks about. This way, surely there's an opportunity for the GPIO values to changes between checking them to see if it's time to block, and actually blocking?
    – Dolda2000
    Commented Nov 13, 2021 at 17:55
  • It's worth observing that a button press (as in, a button pressed by a human being): 1. Does not take place instantaneously, and will last several orders of magnitude more time than that between "read" and "poll" ; 2. Involves multiple changes of state, unless we mean "button down when the user may hold it indefinitely"; 3. Realistically, involves a lot more than just two changes of state due to bounce, and if you are not dealing with bounce (it is clear you are not) you have a defective, highly error prone system to start with.
    – goldilocks
    Commented Nov 25, 2021 at 15:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.