diff --git a/doc/Raspberry-Pi-APRS.pdf b/doc/Raspberry-Pi-APRS.pdf index 3ccff1f..b05c85f 100644 Binary files a/doc/Raspberry-Pi-APRS.pdf and b/doc/Raspberry-Pi-APRS.pdf differ diff --git a/ptt.c b/ptt.c index 16a2386..22e5bd9 100644 --- a/ptt.c +++ b/ptt.c @@ -1,7 +1,7 @@ // // 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 // 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. * + * 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 * * https://www.kernel.org/doc/Documentation/gpio.txt @@ -115,6 +119,8 @@ #include #include #include +#include +#include #ifdef USE_HAMLIB #include @@ -163,38 +169,254 @@ void ptt_set_debug(int debug) ptt_debug_level = debug; } + /*------------------------------------------------------------------- * - * Name: export_gpio + * Name: get_access_to_gpio * - * Purpose: Tell the GPIO subsystem to export a GPIO line for - * us to use, and set the initial state of the GPIO. + * Purpose: Try to get access to the GPIO device. * - * Inputs: gpio - GPIO line to export - * invert: - Is the GPIO active low? - * direction: - 0 for input, 1 for output + * Inputs: path - Path to device node. + * /sys/class/gpio/export + * /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__ +#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) { HANDLE fd; - char stemp[80]; - struct stat finfo; - int err; + const char gpio_export_path[] = "/sys/class/gpio/export"; + char gpio_direction_path[80]; + 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); - 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"); + dw_printf ("GPIO line number %d is invalid. Must be in range of 0 to %d.\n", gpio, MAX_GPIO_NUM - 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); if (write (fd, stemp, strlen(stemp)) != strlen(stemp)) { int e = errno; @@ -202,7 +424,7 @@ void export_gpio(int gpio, int invert, int direction) /* the device node already exists. */ if (e != EBUSY) { 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)); exit (1); } @@ -210,62 +432,118 @@ void export_gpio(int gpio, int invert, int direction) close (fd); /* - Idea for future: - - On the RPi, the device path for GPIO number XX is /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. - - For another similar single board computer, we find the same thing: - https://www.olimex.com/wiki/A20-OLinuXino-LIME#GPIO_under_Linux - - How should we deal with this? Some possibilities: - - (1) The user might explicitly mention the name in direwolf.conf. - (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 - 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 only care about "direction" and "value". + * Added in release 1.4. + * + * 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. + * https://github.com/cubieplayer/Cubian/wiki/GPIO-Introduction + * + * For another similar single board computer, we find the same thing: + * https://www.olimex.com/wiki/A20-OLinuXino-LIME#GPIO_under_Linux + * + * How should we deal with this? Some possibilities: + * + * (1) The user might explicitly mention the name in direwolf.conf. + * (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 + * 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 are finally implementing the third choice. */ - 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) { - int e = errno; - text_color_set(DW_COLOR_ERROR); - dw_printf ("Failed to get status for %s \n", stemp); - dw_printf ("%s\n", strerror(e)); - exit (1); + if (ptt_debug_level >= 2) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Contents of /sys/class/gpio:\n"); } - if (geteuid() != 0) { - 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 these commands:\n"); - dw_printf (" chmod go+rw /sys/class/gpio/gpio%d/direction", gpio); - dw_printf (" chmod go+rw /sys/class/gpio/gpio%d/value", gpio); - exit (1); + num_files = scandir ("/sys/class/gpio", &file_list, NULL, alphasort); + + if (num_files < 0) { + // Something went wrong. Fill in the simple expected name and keep going. + + text_color_set(DW_COLOR_ERROR); + dw_printf ("ERROR! Could not get directory listing for /sys/class/gpio\n"); + + 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 */ - snprintf (stemp, sizeof(stemp), "/sys/class/gpio/gpio%d/direction", gpio); - fd = open(stemp, O_WRONLY); + snprintf (gpio_direction_path, sizeof(gpio_direction_path), "/sys/class/gpio/%s/direction", gpio_name[gpio]); + get_access_to_gpio (gpio_direction_path); + + fd = open(gpio_direction_path, O_WRONLY); if (fd < 0) { int e = errno; text_color_set(DW_COLOR_ERROR); @@ -539,59 +817,11 @@ void ptt_init (struct audio_s *audio_config_p) } if (using_gpio) { - - 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? - */ + get_access_to_gpio ("/sys/class/gpio/export"); - 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 * 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) { 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) { int e = errno; text_color_set(DW_COLOR_ERROR); @@ -1011,15 +1244,17 @@ int get_input (int it, int chan) #else if (save_audio_config_p->achan[chan].ictrl[it].method == PTT_METHOD_GPIO) { 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) { int e = errno; 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)); return -1; } @@ -1033,6 +1268,7 @@ int get_input (int it, int chan) } close (fd); + vtemp[1] = '\0'; if (atoi(vtemp) != save_audio_config_p->achan[chan].ictrl[it].invert) { return 1; }