craspberry-pifopengpiosysfs

Raspberry pi GPIO sysfs, fopen: Permission denied


I'm trying to use raspberry pi GPIOs through sysfs using the C language, but I keep getting fopen: Permission denied when trying to change the GPIO line direction.
The issue occurs when trying to open the file "/sys/class/gpio/gpio18/direction".

Notes:

Normally I would need to put the pin that I want to use in "/sys/class/gpio/export".
And then I need to set its direction (output/input) by writing 1 or 0 to "/sys/class/gpio/gpio18/direction".

To achieve that programmatically I use the following code

#include "io.h"
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>

int main(int argc, char const* argv[]) {
  // export line
  FILE* p_gpio_line;
  if ((p_gpio_line = fopen("/sys/class/gpio/export", "w")) == NULL) {
    printf("Cannot open export file.\n");
    perror("fopen");
    exit(1);
  }
  rewind(p_gpio_line);
  fwrite("18", sizeof(char), 2, p_gpio_line);
  fclose(p_gpio_line);

  // set direction
  FILE* p_gpio_direction;
  if ((p_gpio_direction = fopen("/sys/class/gpio/gpio18/direction", "r+")) == NULL) {
    printf("Cannot open direction file.\n");
    perror("fopen");
    exit(1);
  }
  return 0;
}

The first time it executes I get

Cannot open direction file.
fopen: Permission denied

The second time it works fine because the GPIO line is exported before the program is executed (from the first execution)
It also works fine if I manually export the GPIO line from the terminal

terminal output


Solution

  • My comment about perror() remains in effect, but indeed in your example fopen() returns EACCES ("Permission denied").

    Group gpio and permissions for /sys/class/gpio are specific for Raspberry Pi distro. They are set by udev rule in /etc/udev/rules.d/99-com.rules:

    SUBSYSTEM=="gpio", ACTION=="add", PROGRAM="/bin/sh -c 'chgrp -R gpio /sys%p && chmod -R g=u /sys%p'"
    

    Udev rule is applied asynchronously after direction file appears. You are trying to open the file while permissions aren't yet set.

    I'd suggest to use character device GPIO interface with libgpiod instead of deprecated sysfs interface.

    If you still want to use sysfs interface, straightforward solution is to retry fopen() until it succeed (or fail with error other than EACCES, or timeout). For example:

    // Open timeout, ms
    #define DIR_OPEN_TIMEOUT_MS 3000
    // Delay between retries, ms
    #define DIR_POLL_DELAY_MS   100
    
    ...
        // set direction
        FILE* p_gpio_direction;
        unsigned int retries_left = DIR_OPEN_TIMEOUT_MS / DIR_POLL_DELAY_MS;
        while (1)
        {
            p_gpio_direction = fopen("/sys/class/gpio/gpio18/direction", "r+");
    
            if (p_gpio_direction)
                break;
    
            if ((errno != EACCES) || (retries_left-- == 0)) {
                printf("Cannot open direction file: %m\n");
                exit(1);
            }
    
            const struct timespec rqt = {
                .tv_sec  = DIR_POLL_DELAY_MS / 1000,
                .tv_nsec = 1000000L * (DIR_POLL_DELAY_MS % 1000)
            };
            if (nanosleep(&rqt, NULL) == -1)
                perror("nanosleep()");
        }
    ...