How to use poll() with sysfs interface to input GPIO pin to handle a switch press event

I am working with the DragonBoard 410C using the Android 5.1 build 99 from the 96Boards website that I installed. I’m writing an example application in Kotlin with a simple UI to turn LEDs on and off as well as to blink them.

I am using the sysfs interface reading and writing to the GPIO pin pseudo files in /sys/class/gpio.

Now I want to detect the pressing of a momentary contact switch on the breadboard and when the switch press event is detected to then do something like update the displayed information on the screen or blink an LED.

I have posted a question in StackOverFlow that provides the details as to what I am doing.
https://stackoverflow.com/questions/64028130/how-to-use-poll2-or-select2-service-call-to-watch-a-pseudo-file-for-changes

And this is an article I am updating to document what I’m doing.
https://www.codeproject.com/Articles/5277258/Using-Windows-10-for-Development-with-DragonBoard

The basics are to set up the proper environment:

  • set the event criteria in /edge pseudo file of the GPIO pin, as in “rising” for a rising edge as event trigger
  • start a poll(2) on the /value pseudo file of the GPIO pin

When I press the switch, the kernel should detect the rising voltage on the pin and then update the /value pseudo file which causes the poll(2) to return indicating that a POLLIN event for the file descriptor has been received.

However in my testing, that does not seem to be what is happening. Instead the poll(2) call immediately returns back with both POLLIN and POLLERR set as events.

The poll(2) functionality is in a Native C++ function using JNI which is then loaded into Kotlin for use.

My question is does the sysfs interface in the Android 5.1 build 99 supports using poll(2) with a /sys/class/gpio pseudo file?

I’m afraid I can’t tell you if the old Android builds support polling (although the presence of the edge file suggests so).

On the other hand… it does look like you are not polling correctly. All the documentation for the sysfs attributes that I have seen say to poll for POLLPRI|POLLERR (or if you are using select to list the fd in except_fds):
https://www.kernel.org/doc/Documentation/gpio/sysfs.txt

I have modified the source code to do POLLPR | POLLERR and I am still seeing that the poll(2) is returning immediately with both POLLPRI and POLLERR set in the revents member which provides the return status.

However when I read the /value of the GPIO pin, I am seeing the correct value returned.

When I read the settings, the /edge is set to rising, /active_low is set to 0 and /direction is set to in. The hex dump of the revents member is 0xa which indicates bits 2 and 8 are set which, from my testing, indicates POLLPRI and POLLERR respectively.

The circuit I am testing is a wire from pin 23 to an LED with a resistor and then to ground. Then I have a wire from pin 26 to the anode of the LED. The LED tells me if there is current flowing by being either lit or unlit. The idea is to turn the Edge Detect on using poll(2) and then press the button to turn on the LED. When the LED lights, I should then see the poll(2) return as long as I press the LED On button within 10 seconds of pressing the Edge Detect button.

However poll(2) is returning immediately. I am seeing a poll status of PollErrorPOLLERRDEFLT because both POLLPRI and POLLERR are set according to my debug output.

This is the function with the poll(2) in my latest testing.

int PollFileService::PollFileCheck(const char *pathName, int timeMilliSec /* = -1 */)
{
    struct pollfd fdList[] = {
            {fd, POLLPRI | POLLERR, 0},
            {0}
        };
    nfds_t nfds = 1;

    if (fd < 0 && pathName) {
        fd = open (pathName, O_RDONLY);
        fdList[0].fd = fd;
    }
    iPollStatus = PollErrorUNKNOWN;
    int iRet = poll(fdList, nfds, timeMilliSec);

    if (iRet == 0) {
        iPollStatus = PollTimeOut;
    } else if (iRet < 0) {
        switch (errno) {
            case EFAULT:
                iPollStatus = PollErrorEFAULT;
                break;
            case EINTR:
                iPollStatus = PollErrorEINTR;
                break;
            case EINVAL:
                iPollStatus = PollErrorEINVAL;
                break;
            case ENOMEM:
                iPollStatus = PollErrorENOMEM;
                break;
            default:
                iPollStatus = PollErrorUNKNOWN;
                break;
        }
    } else if (iRet > 0) {
        // successful call now determine what we should return.
        iPollRevents = fdList[0].revents; /* & (POLLIN | POLLPRI | POLLERR); */
        switch (fdList[0].revents & (POLLIN | POLLPRI | POLLERR /* | POLLNVAL | POLLHUP*/)) {
            case (POLLIN):                // value of 1
            case (POLLPRI):               // value of 2
            case (POLLIN | POLLPRI):       // value of 3
                iPollStatus = PollSuccess;
                break;

            case (POLLHUP):
                iPollStatus = PollErrorPOLLHUP;
                break;
            case (POLLERR):               // value of 8
                iPollStatus = PollErrorPOLLERR;
                break;
            case (POLLNVAL):
                iPollStatus = PollErrorPOLLNVAL;
                break;
            case (POLLERR | POLLNVAL):
                iPollStatus = PollErrorPOLLERRNVAL;
                break;

            default:
                iPollStatus = PollErrorPOLLERRDEFLT;
                break;
        }
    }

    return iPollStatus;
}

I think you need to read the attribute before you wait for it to change value. In other words there is unread status for the file descriptor you just opened and poll is (correctly) telling you to clear that out before sleeping.

1 Like

Lol. I never would have thought of trying that! Thank you.

I’ll let you know how it goes when I try it this evening.

I have tried your proposed solution by adding a read(2) from the file descriptor after opening the file and before starting the poll(2). The read(2) is using an unsigned char buffer size 256 and the input is just ignored.

// with a edge triggered GPIO that we are going to use the poll(2)
// function to wait on an event, we need to read from the
// pin before we do the poll(2). If the read is not done then
// the poll(2) returns with both POLLPRI and POLLERR set in the
// revents member. however if we read first then do the poll2()
// the poll(2) will wait for the event, input voltage change with
// either a rising edge or a falling edge, depending on the setting
// in the /edge pseudo file.

unsigned char tempbuff[256] = {0};
ssize_t iCount = read (fdList[0].fd, tempbuff, 255);

What I am seeing is that the poll(2) is now working as expected in that it starts and is timing out.

Unfortunately, I need to now learn about Kotlin coroutines and multi-threading in order to toss off a side thread which will do the poll(2) and then send an event back to my Kotlin program indicating whether there was a time out or an error or if a rising voltage was seen.

Thank you for your help.