Take advantage of new 'gpio' group and new /sys/class/gpio ownership in Raspbian Jessie.

Handle more complicated gpio naming for CubieBoard, etc.
This commit is contained in:
WB2OSZ 2017-03-05 14:32:58 -05:00
parent dc292ffcc9
commit 58c2707f7d
2 changed files with 354 additions and 118 deletions

Binary file not shown.

472
ptt.c
View File

@ -1,7 +1,7 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // This file is part of Dire Wolf, an amateur radio packet TNC.
// //
// Copyright (C) 2011, 2013, 2014, 2015, 2016 John Langner, WB2OSZ // Copyright (C) 2011, 2013, 2014, 2015, 2016, 2017 John Langner, WB2OSZ
// //
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by // it under the terms of the GNU General Public License as published by
@ -48,6 +48,10 @@
* *
* Version 1.4: The spare "future" indicator is now used when connected to another station. * Version 1.4: The spare "future" indicator is now used when connected to another station.
* *
* Take advantage of the new 'gpio' group and new /sys/class/gpio protections in Raspbian Jessie.
*
* Handle more complicated gpio node names for CubieBoard, etc.
*
* References: http://www.robbayer.com/files/serial-win.pdf * References: http://www.robbayer.com/files/serial-win.pdf
* *
* https://www.kernel.org/doc/Documentation/gpio.txt * https://www.kernel.org/doc/Documentation/gpio.txt
@ -115,6 +119,8 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
#include <errno.h> #include <errno.h>
#include <grp.h>
#include <dirent.h>
#ifdef USE_HAMLIB #ifdef USE_HAMLIB
#include <hamlib/rig.h> #include <hamlib/rig.h>
@ -163,38 +169,254 @@ void ptt_set_debug(int debug)
ptt_debug_level = debug; ptt_debug_level = debug;
} }
/*------------------------------------------------------------------- /*-------------------------------------------------------------------
* *
* Name: export_gpio * Name: get_access_to_gpio
* *
* Purpose: Tell the GPIO subsystem to export a GPIO line for * Purpose: Try to get access to the GPIO device.
* us to use, and set the initial state of the GPIO.
* *
* Inputs: gpio - GPIO line to export * Inputs: path - Path to device node.
* invert: - Is the GPIO active low? * /sys/class/gpio/export
* direction: - 0 for input, 1 for output * /sys/class/gpio/unexport
* /sys/class/gpio/gpio??/direction
* /sys/class/gpio/gpio??/value
* *
* Outputs: None. * Description: First see if we have access thru the usual uid/gid/mode method.
* If that fails, we try a hack where we use "sudo chmod ..." to open up access.
* That requires that sudo be configured to work without a password.
* That's the case for 'pi' user in Raspbian but not not be for other boards / operating systems.
*
* Debug: Use the "-doo" command line option.
* *
*------------------------------------------------------------------*/ *------------------------------------------------------------------*/
#ifndef __WIN32__ #ifndef __WIN32__
#define MAX_GROUPS 50
static void get_access_to_gpio (const char *path)
{
static int my_uid = -1;
static int my_gid = -1;
static gid_t my_groups[MAX_GROUPS];
static int num_groups = 0;
static int first_time = 1;
struct stat finfo;
int i;
char cmd[80];
int err;
/*
* Does path even exist?
*/
if (stat(path, &finfo) < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Can't get properties of %s.\n", path);
dw_printf ("This system is not configured with the GPIO user interface.\n");
dw_printf ("Use a different method for PTT control.\n");
exit (1);
}
if (first_time) {
// No need to fetch same information each time. Cache it.
my_uid = geteuid();
my_gid = getegid();
num_groups = getgroups (MAX_GROUPS, my_groups);
if (num_groups < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("getgroups() failed to get supplementary groups, errno=%d\n", errno);
num_groups = 0;
}
first_time = 0;
}
if (ptt_debug_level >= 2) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("%s: uid=%d, gid=%d, mode=o%o\n", path, finfo.st_uid, finfo.st_gid, finfo.st_mode);
dw_printf ("my uid=%d, gid=%d, supplementary groups=", my_uid, my_gid);
for (i = 0; i < num_groups; i++) {
dw_printf (" %d", my_groups[i]);
}
dw_printf ("\n");
}
/*
* Do we have permission to access it?
*
* On Debian 7 (Wheezy) we see this:
*
* $ ls -l /sys/class/gpio/export
* --w------- 1 root root 4096 Feb 27 12:31 /sys/class/gpio/export
*
*
* Only root can write to it.
* Our work-around is change the protection so that everyone can write.
* This requires that the current user can use sudo without a password.
* This has been the case for the predefined "pi" user but can be a problem
* when people add new user names.
* Other operating systems could have different default configurations.
*
* A better solution is available in Debian 8 (Jessie). The group is now "gpio"
* so anyone in that group can now write to it.
*
* $ ls -l /sys/class/gpio/export
* -rwxrwx--- 1 root gpio 4096 Mar 4 21:12 /sys/class/gpio/export
*
*
* First see if we can access it by the usual file protection rules.
* If not, we will try the "sudo chmod go+rw ..." hack.
*
*/
/*
* Do I have access?
* We could just try to open for write but this gives us more debugging information.
*/
if ((my_uid == finfo.st_uid) && (finfo.st_mode & S_IWUSR)) { // user write 00200
if (ptt_debug_level >= 2) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("My uid matches and we have user write permission.\n");
}
return;
}
if ((my_gid == finfo.st_gid) && (finfo.st_mode & S_IWGRP)) { // group write 00020
if (ptt_debug_level >= 2) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("My primary gid matches and we have group write permission.\n");
}
return;
}
for (i = 0; i < num_groups; i++) {
if ((my_groups[i] == finfo.st_gid) && (finfo.st_mode & S_IWGRP)) { // group write 00020
if (ptt_debug_level >= 2) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("My supplemental group %d matches and we have group write permission.\n", my_groups[i]);
}
return;
}
}
if (finfo.st_mode & S_IWOTH) { // other write 00002
if (ptt_debug_level >= 2) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("We have other write permission.\n");
}
return;
}
/*
* We don't have permission.
* Try a hack which requires that the user be set up to use sudo without a password.
*/
if (ptt_debug_level >= 2) {
text_color_set(DW_COLOR_ERROR); // debug message but different color so it stands out.
dw_printf ("Trying 'sudo chmod go+rw %s' hack.\n", path);
}
snprintf (cmd, sizeof(cmd), "sudo chmod go+rw %s", path);
err = system (cmd);
(void)err; // suppress warning about not using result.
/*
* I don't trust status coming back from system() so we will check the mode again.
*/
if (stat(path, &finfo) < 0) {
/* Unexpected because we could do it before. */
text_color_set(DW_COLOR_ERROR);
dw_printf ("This system is not configured with the GPIO user interface.\n");
dw_printf ("Use a different method for PTT control.\n");
exit (1);
}
/* Did we succeed in changing the protection? */
if ( (finfo.st_mode & 0266) != 0266) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("You don't have the necessary permission to access GPIO.\n");
dw_printf ("There are three different solutions: \n");
dw_printf (" 1. Run as root. (not recommended)\n");
dw_printf (" 2. If operating system has 'gpio' group, add your user id to it.\n");
dw_printf (" 3. Configure your user id for sudo without a password.\n");
dw_printf ("\n");
dw_printf ("Read the documentation and try -doo command line option for debugging details.\n");
exit (1);
}
}
#endif
/*-------------------------------------------------------------------
*
* Name: export_gpio
*
* Purpose: Tell the GPIO subsystem to export a GPIO line for
* us to use, and set the initial state of the GPIO.
*
* Inputs: gpio - GPIO line number to export.
* invert: - Is the GPIO active low?
* direction: - 0 for input, 1 for output
*
* Outputs: gpio_name - See below.
*
*------------------------------------------------------------------*/
#ifndef __WIN32__
#define MAX_GPIO_NUM 100 // 76 would handle any case I've seen.
#define MAX_GPIO_NAME_LEN 16 // 12 would handle any case I've seen.
// Raspberry Pi was easy. GPIO 24 has the name gpio24.
// Others, such as the Cubieboard, take a little more effort.
// The name might be gpio24_ph11 meaning connector H, pin 11.
// When we "export" GPIO number, we will store the corresponding device name
// here for future use when we want to access it.
// I never saw any references to anything above gpio75 so we will take the easy
// way out and simply index into a fixed size array.
static char gpio_name[MAX_GPIO_NUM][MAX_GPIO_NAME_LEN];
void export_gpio(int gpio, int invert, int direction) void export_gpio(int gpio, int invert, int direction)
{ {
HANDLE fd; HANDLE fd;
char stemp[80]; const char gpio_export_path[] = "/sys/class/gpio/export";
struct stat finfo; char gpio_direction_path[80];
int err; char stemp[16];
fd = open("/sys/class/gpio/export", O_WRONLY);
if (fd < 0) { if (gpio < 0 || gpio >= MAX_GPIO_NUM) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Permissions do not allow ordinary users to access GPIO.\n"); dw_printf ("GPIO line number %d is invalid. Must be in range of 0 to %d.\n", gpio, MAX_GPIO_NUM - 1);
dw_printf ("Log in as root and type this command:\n");
dw_printf (" chmod go+w /sys/class/gpio/export /sys/class/gpio/unexport\n");
exit (1); exit (1);
} }
get_access_to_gpio (gpio_export_path);
fd = open(gpio_export_path, O_WRONLY);
if (fd < 0) {
// Not expected. Above should have obtained permission or exited.
text_color_set(DW_COLOR_ERROR);
dw_printf ("Permissions do not allow access to GPIO.\n");
exit (1);
}
snprintf (stemp, sizeof(stemp), "%d", gpio); snprintf (stemp, sizeof(stemp), "%d", gpio);
if (write (fd, stemp, strlen(stemp)) != strlen(stemp)) { if (write (fd, stemp, strlen(stemp)) != strlen(stemp)) {
int e = errno; int e = errno;
@ -202,7 +424,7 @@ void export_gpio(int gpio, int invert, int direction)
/* the device node already exists. */ /* the device node already exists. */
if (e != EBUSY) { if (e != EBUSY) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Error writing \"%s\" to /sys/class/gpio/export, errno=%d\n", stemp, e); dw_printf ("Error writing \"%s\" to %s, errno=%d\n", stemp, gpio_export_path, e);
dw_printf ("%s\n", strerror(e)); dw_printf ("%s\n", strerror(e));
exit (1); exit (1);
} }
@ -210,62 +432,118 @@ void export_gpio(int gpio, int invert, int direction)
close (fd); close (fd);
/* /*
Idea for future: * Added in release 1.4.
*
On the RPi, the device path for GPIO number XX is /sys/class/gpio/gpioXX. * On the RPi, the device path for GPIO number XX is simply /sys/class/gpio/gpioXX.
There was a report that it is different for the Cubieboard. For instance *
GPIO 61 has gpio61_pi13 in the path. This indicates connector "i" pin 13. * There was a report that it is different for the CubieBoard. For instance
* GPIO 61 has gpio61_pi13 in the path. This indicates connector "i" pin 13.
For another similar single board computer, we find the same thing: * https://github.com/cubieplayer/Cubian/wiki/GPIO-Introduction
https://www.olimex.com/wiki/A20-OLinuXino-LIME#GPIO_under_Linux *
* For another similar single board computer, we find the same thing:
How should we deal with this? Some possibilities: * https://www.olimex.com/wiki/A20-OLinuXino-LIME#GPIO_under_Linux
*
(1) The user might explicitly mention the name in direwolf.conf. * How should we deal with this? Some possibilities:
(2) We might be able to find the names in some system device config file. *
(3) Get a directory listing of /sys/class/gpio then search for a * (1) The user might explicitly mention the name in direwolf.conf.
matching name. Suppose we wanted GPIO 61. First look for an exact * (2) We might be able to find the names in some system device config file.
match to "gpio61". If that is not found, look for something * (3) Get a directory listing of /sys/class/gpio then search for a
matching the pattern "gpio61_*". * matching name. Suppose we wanted GPIO 61. First look for an exact
*/ * match to "gpio61". If that is not found, look for something
* matching the pattern "gpio61_*".
/* *
* We will have the same permission problem if not root. * We are finally implementing the third choice.
* We only care about "direction" and "value".
*/ */
snprintf (stemp, sizeof(stemp), "sudo chmod go+rw /sys/class/gpio/gpio%d/direction", gpio);
err = system (stemp);
snprintf (stemp, sizeof(stemp), "sudo chmod go+rw /sys/class/gpio/gpio%d/value", gpio);
err = system (stemp);
(void)err;
snprintf (stemp, sizeof(stemp), "/sys/class/gpio/gpio%d/value", gpio); struct dirent **file_list;
int num_files;
int i;
int ok = 0;
if (stat(stemp, &finfo) < 0) { if (ptt_debug_level >= 2) {
int e = errno; text_color_set(DW_COLOR_DEBUG);
text_color_set(DW_COLOR_ERROR); dw_printf ("Contents of /sys/class/gpio:\n");
dw_printf ("Failed to get status for %s \n", stemp);
dw_printf ("%s\n", strerror(e));
exit (1);
} }
if (geteuid() != 0) { num_files = scandir ("/sys/class/gpio", &file_list, NULL, alphasort);
if ( ! (finfo.st_mode & S_IWOTH)) {
text_color_set(DW_COLOR_ERROR); if (num_files < 0) {
dw_printf ("Permissions do not allow ordinary users to access GPIO.\n"); // Something went wrong. Fill in the simple expected name and keep going.
dw_printf ("Log in as root and type these commands:\n");
dw_printf (" chmod go+rw /sys/class/gpio/gpio%d/direction", gpio); text_color_set(DW_COLOR_ERROR);
dw_printf (" chmod go+rw /sys/class/gpio/gpio%d/value", gpio); dw_printf ("ERROR! Could not get directory listing for /sys/class/gpio\n");
exit (1);
snprintf (gpio_name[gpio], sizeof(gpio_name[gpio]), "gpio%d", gpio);
num_files = 0;
ok = 1;
}
else {
if (ptt_debug_level >= 2) {
text_color_set(DW_COLOR_DEBUG);
for (i = 0; i < num_files; i++) {
dw_printf("\t%s\n", file_list[i]->d_name);
}
} }
// Look for exact name gpioNN
char lookfor[16];
snprintf (lookfor, sizeof(lookfor), "gpio%d", gpio);
for (i = 0; i < num_files && ! ok; i++) {
if (strcmp(lookfor, file_list[i]->d_name) == 0) {
strlcpy (gpio_name[gpio], file_list[i]->d_name, sizeof(gpio_name[gpio]));
ok = 1;
}
}
// If not found, Look for gpioNN_*
snprintf (lookfor, sizeof(lookfor), "gpio%d_", gpio);
for (i = 0; i < num_files && ! ok; i++) {
if (strncmp(lookfor, file_list[i]->d_name, strlen(lookfor)) == 0) {
strlcpy (gpio_name[gpio], file_list[i]->d_name, sizeof(gpio_name[gpio]));
ok = 1;
}
}
// Free the storage allocated by scandir().
for (i = 0; i < num_files; i++) {
free (file_list[i]);
}
free (file_list);
}
/*
* We should now have the corresponding node name.
*/
if (ok) {
if (ptt_debug_level >= 2) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Path for gpio number %d is /sys/class/gpio/%s\n", gpio, gpio_name[gpio]);
}
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR! Could not find Path for gpio number %d.n", gpio);
exit (1);
} }
/* /*
* Set output direction and initial state * Set output direction and initial state
*/ */
snprintf (stemp, sizeof(stemp), "/sys/class/gpio/gpio%d/direction", gpio); snprintf (gpio_direction_path, sizeof(gpio_direction_path), "/sys/class/gpio/%s/direction", gpio_name[gpio]);
fd = open(stemp, O_WRONLY); get_access_to_gpio (gpio_direction_path);
fd = open(gpio_direction_path, O_WRONLY);
if (fd < 0) { if (fd < 0) {
int e = errno; int e = errno;
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
@ -539,59 +817,11 @@ void ptt_init (struct audio_s *audio_config_p)
} }
if (using_gpio) { if (using_gpio) {
get_access_to_gpio ("/sys/class/gpio/export");
struct stat finfo;
/*
* Normally the device nodes are set up for access
* only by root. Try to change it here so we don't
* burden user with another configuration step.
*
* Does /sys/class/gpio/export even exist?
*/
if (stat("/sys/class/gpio/export", &finfo) < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("This system is not configured with the GPIO user interface.\n");
dw_printf ("Use a different method for PTT control.\n");
exit (1);
}
/*
* Do we have permission to access it?
*
* pi@raspberrypi /sys/class/gpio $ ls -l
* total 0
* --w------- 1 root root 4096 Aug 20 07:59 export
* lrwxrwxrwx 1 root root 0 Aug 20 07:59 gpiochip0 -> ../../devices/virtual/gpio/gpiochip0
* --w------- 1 root root 4096 Aug 20 07:59 unexport
*/
if (geteuid() != 0) {
if ( ! (finfo.st_mode & S_IWOTH)) {
int err;
/* Try to change protection. */
err = system ("sudo chmod go+w /sys/class/gpio/export /sys/class/gpio/unexport");
(void)err;
if (stat("/sys/class/gpio/export", &finfo) < 0) {
/* Unexpected because we could do it before. */
text_color_set(DW_COLOR_ERROR);
dw_printf ("This system is not configured with the GPIO user interface.\n");
dw_printf ("Use a different method for PTT control.\n");
exit (1);
}
/* Did we succeed in changing the protection? */
if ( ! (finfo.st_mode & S_IWOTH)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Permissions do not allow ordinary users to access GPIO.\n");
dw_printf ("Log in as root and type this command:\n");
dw_printf (" chmod go+w /sys/class/gpio/export /sys/class/gpio/unexport\n");
exit (1);
}
}
}
} }
/* /*
* We should now be able to create the device nodes for * We should now be able to create the device nodes for
* the pins we want to use. * the pins we want to use.
@ -892,11 +1122,14 @@ void ptt_set (int ot, int chan, int ptt_signal)
if (save_audio_config_p->achan[chan].octrl[ot].ptt_method == PTT_METHOD_GPIO) { if (save_audio_config_p->achan[chan].octrl[ot].ptt_method == PTT_METHOD_GPIO) {
int fd; int fd;
char stemp[80]; char gpio_value_path[80];
char stemp[16];
snprintf (stemp, sizeof(stemp), "/sys/class/gpio/gpio%d/value", save_audio_config_p->achan[chan].octrl[ot].ptt_gpio); snprintf (gpio_value_path, sizeof(gpio_value_path), "/sys/class/gpio/%s/value", gpio_name[save_audio_config_p->achan[chan].octrl[ot].ptt_gpio]);
fd = open(stemp, O_WRONLY); get_access_to_gpio (gpio_value_path);
fd = open(gpio_value_path, O_WRONLY);
if (fd < 0) { if (fd < 0) {
int e = errno; int e = errno;
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
@ -1011,15 +1244,17 @@ int get_input (int it, int chan)
#else #else
if (save_audio_config_p->achan[chan].ictrl[it].method == PTT_METHOD_GPIO) { if (save_audio_config_p->achan[chan].ictrl[it].method == PTT_METHOD_GPIO) {
int fd; int fd;
char stemp[80]; char gpio_value_path[80];
snprintf (stemp, sizeof(stemp), "/sys/class/gpio/gpio%d/value", save_audio_config_p->achan[chan].ictrl[it].gpio); snprintf (gpio_value_path, sizeof(gpio_value_path), "/sys/class/gpio/%s/value", gpio_name[save_audio_config_p->achan[chan].ictrl[it].gpio]);
fd = open(stemp, O_RDONLY); get_access_to_gpio (gpio_value_path);
fd = open(gpio_value_path, O_RDONLY);
if (fd < 0) { if (fd < 0) {
int e = errno; int e = errno;
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("Error opening %s to check input.\n", stemp); dw_printf ("Error opening %s to check input.\n", gpio_value_path);
dw_printf ("%s\n", strerror(e)); dw_printf ("%s\n", strerror(e));
return -1; return -1;
} }
@ -1033,6 +1268,7 @@ int get_input (int it, int chan)
} }
close (fd); close (fd);
vtemp[1] = '\0';
if (atoi(vtemp) != save_audio_config_p->achan[chan].ictrl[it].invert) { if (atoi(vtemp) != save_audio_config_p->achan[chan].ictrl[it].invert) {
return 1; return 1;
} }