diff --git a/.gitignore b/.gitignore index 659c845b..b917a7ab 100644 --- a/.gitignore +++ b/.gitignore @@ -109,5 +109,5 @@ $RECYCLE.BIN/ *.dSYM # cmake -build/ +build*/ tmp/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c01045b..ce92decb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -320,6 +320,14 @@ else() set(HAMLIB_LIBRARIES "") endif() +find_package(gpiod) +if(GPIOD_FOUND) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_GPIOD") +else() + set(GPIOD_INCLUDE_DIRS "") + set(GPIOD_LIBRARIES "") +endif() + if(LINUX) find_package(ALSA REQUIRED) if(ALSA_FOUND) diff --git a/cmake/modules/FindCompiler.cmake b/cmake/modules/FindCompiler.cmake index fe036e4b..91e1b89c 100644 --- a/cmake/modules/FindCompiler.cmake +++ b/cmake/modules/FindCompiler.cmake @@ -5,9 +5,9 @@ elseif(NOT DEFINED C_GCC AND CMAKE_CXX_COMPILER_ID MATCHES "GNU") set(C_GCC 1) elseif(NOT DEFINED C_MSVC AND CMAKE_CXX_COMPILER_ID MATCHES "MSVC") set(C_MSVC 1) - if(MSVC_VERSION GREATER_EQUAL 1920 AND MSVC_VERSION LESS_EQUAL 1929) + if(MSVC_VERSION GREATER 1919 AND MSVC_VERSION LESS 1926) set(VS2019 ON) - elseif(MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS_EQUAL 1919) + elseif(MSVC_VERSION GREATER 1910 AND MSVC_VERSION LESS 1919) set(VS2017 ON) elseif(MSVC_VERSION GREATER 1899 AND MSVC_VERSION LESS 1910) set(VS2015 ON) diff --git a/cmake/modules/Findgpiod.cmake b/cmake/modules/Findgpiod.cmake new file mode 100644 index 00000000..bf5be305 --- /dev/null +++ b/cmake/modules/Findgpiod.cmake @@ -0,0 +1,23 @@ +# - Try to find libgpiod +# Once done this will define +# GPIOD_FOUND - System has libgpiod +# GPIOD_INCLUDE_DIRS - The libgpiod include directories +# GPIOD_LIBRARIES - The libraries needed to use libgpiod +# GPIOD_DEFINITIONS - Compiler switches required for using libgpiod + +find_package(PkgConfig) +pkg_check_modules(PC_GPIOD QUIET gpiod) + +find_path(GPIOD_INCLUDE_DIR gpiod.h) +find_library(GPIOD_LIBRARY NAMES gpiod) + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set GPIOD_FOUND to TRUE +# if all listed variables are TRUE +find_package_handle_standard_args(gpiod DEFAULT_MSG + GPIOD_LIBRARY GPIOD_INCLUDE_DIR) + +mark_as_advanced(GPIOD_INCLUDE_DIR GPIOD_LIBRARY) + +set(GPIOD_LIBRARIES ${GPIOD_LIBRARY}) +set(GPIOD_INCLUDE_DIRS ${GPIOD_INCLUDE_DIR}) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a2c3963d..5320a163 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,6 +10,7 @@ include_directories( ${PORTAUDIO_INCLUDE_DIRS} ${SNDIO_INCLUDE_DIRS} ${CUSTOM_GEOTRANZ_DIR} + ${GPIOD_INCLUDE_DIRS} ${CUSTOM_HIDAPI_DIR} ) @@ -155,6 +156,7 @@ target_link_libraries(direwolf ${ALSA_LIBRARIES} ${UDEV_LIBRARIES} ${PORTAUDIO_LIBRARIES} + ${GPIOD_LIBRARIES} ${SNDIO_LIBRARIES} ${AVAHI_LIBRARIES} ) diff --git a/src/audio.h b/src/audio.h index cb5ca94e..4fc05708 100644 --- a/src/audio.h +++ b/src/audio.h @@ -28,7 +28,8 @@ enum ptt_method_e { PTT_METHOD_NONE, /* VOX or no transmit. */ PTT_METHOD_SERIAL, /* Serial port RTS or DTR. */ - PTT_METHOD_GPIO, /* General purpose I/O, Linux only. */ + PTT_METHOD_GPIO, /* General purpose I/O using sysfs, deprecated after 2020, Linux only. */ + PTT_METHOD_GPIOD, /* General purpose I/O, using libgpiod, Linux only. */ PTT_METHOD_LPT, /* Parallel printer port, Linux only. */ PTT_METHOD_HAMLIB, /* HAMLib, Linux only. */ PTT_METHOD_CM108 }; /* GPIO pin of CM108/CM119/etc. Linux only. */ @@ -74,16 +75,23 @@ struct audio_s { /* Properties of the sound device. */ - int defined; /* Was device defined? */ - /* First one defaults to yes. */ + int defined; /* Was device defined? 0=no. >0 for yes. */ + /* First channel defaults to 2 for yes with default config. */ + /* 1 means it was defined by user. */ + + int copy_from; /* >=0 means copy contents from another audio device. */ + /* In this case we don't have device names, below. */ + /* Num channels, samples/sec, and bit/sample are copied from */ + /* original device and can't be changed. */ + /* -1 for normal case. */ char adevice_in[80]; /* Name of the audio input device (or file?). */ - /* TODO: Can be "-" to read from stdin. */ + /* Can be udp:nnn for UDP or "-" to read from stdin. */ char adevice_out[80]; /* Name of the audio output device (or file?). */ int num_channels; /* Should be 1 for mono or 2 for stereo. */ - int samples_per_sec; /* Audio sampling rate. Typically 11025, 22050, or 44100. */ + int samples_per_sec; /* Audio sampling rate. Typically 11025, 22050, 44100, or 48000. */ int bits_per_sample; /* 8 (unsigned char) or 16 (signed short). */ } adev[MAX_ADEVS]; @@ -304,6 +312,7 @@ struct audio_s { /* the case for CubieBoard where it was longer. */ /* This is filled in by ptt_init so we don't have to */ /* recalculate it each time we access it. */ + /* Also GPIO chip name for GPIOD method. Looks like 'gpiochip4' */ /* This could probably be collapsed into ptt_device instead of being separate. */ diff --git a/src/config.c b/src/config.c index 739eb2f6..747d0e60 100644 --- a/src/config.c +++ b/src/config.c @@ -1842,6 +1842,45 @@ void config_init (char *fname, struct audio_s *p_audio_config, } p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_GPIO; #endif + } + else if (strcasecmp(t, "GPIOD") == 0) { +#if __WIN32__ + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file line %d: %s with GPIOD is only available on Linux.\n", line, otname); +#else +#if defined(USE_GPIOD) + t = split(NULL,0); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file line %d: Missing GPIO chip name for %s.\n", line, otname); + dw_printf ("Use the \"gpioinfo\" command to get a list of gpio chip names and corresponding I/O lines.\n"); + continue; + } + strlcpy(p_audio_config->achan[channel].octrl[ot].out_gpio_name, t, + sizeof(p_audio_config->achan[channel].octrl[ot].out_gpio_name)); + + t = split(NULL,0); + if (t == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Config file line %d: Missing GPIO number for %s.\n", line, otname); + continue; + } + + if (*t == '-') { + p_audio_config->achan[channel].octrl[ot].out_gpio_num = atoi(t+1); + p_audio_config->achan[channel].octrl[ot].ptt_invert = 1; + } + else { + p_audio_config->achan[channel].octrl[ot].out_gpio_num = atoi(t); + p_audio_config->achan[channel].octrl[ot].ptt_invert = 0; + } + p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_GPIOD; +#else + text_color_set(DW_COLOR_ERROR); + dw_printf ("Application was not built with optional support for GPIOD.\n"); + dw_printf ("Install packages gpiod and libgpiod-dev, remove 'build' subdirectory, then rebuild.\n"); +#endif /* USE_GPIOD*/ +#endif /* __WIN32__ */ } else if (strcasecmp(t, "LPT") == 0) { diff --git a/src/direwolf.c b/src/direwolf.c index c6ed9d4d..c8bb3a1b 100644 --- a/src/direwolf.c +++ b/src/direwolf.c @@ -306,7 +306,7 @@ int main (int argc, char *argv[]) //dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION); -#if defined(ENABLE_GPSD) || defined(USE_HAMLIB) || defined(USE_CM108) || USE_AVAHI_CLIENT || USE_MACOS_DNSSD +#if defined(ENABLE_GPSD) || defined(USE_HAMLIB) || defined(USE_CM108) || USE_AVAHI_CLIENT || USE_MACOS_DNSSD || USE_GPIOD dw_printf ("Includes optional support for: "); #if defined(ENABLE_GPSD) dw_printf (" gpsd"); @@ -317,6 +317,9 @@ int main (int argc, char *argv[]) #if defined(USE_CM108) dw_printf (" cm108-ptt"); #endif + #if defined(USE_GPIOD) + dw_printf (" libgpiod"); + #endif #if (USE_AVAHI_CLIENT|USE_MACOS_DNSSD) dw_printf (" dns-sd"); #endif diff --git a/src/dlq.c b/src/dlq.c index f56b8649..aa25b84f 100644 --- a/src/dlq.c +++ b/src/dlq.c @@ -60,6 +60,8 @@ /* The queue is a linked list of these. */ static struct dlq_item_s *queue_head = NULL; /* Head of linked list for queue. */ +static struct dlq_item_s *queue_tail = NULL; /* Tail of linked list for queue. */ +int queue_length = 0; /* Count of items in queue */ #if __WIN32__ @@ -75,8 +77,6 @@ static pthread_mutex_t dlq_mutex; /* Critical section for updating queues. */ static pthread_cond_t wake_up_cond; /* Notify received packet processing thread when queue not empty. */ -static pthread_mutex_t wake_up_mutex; /* Required by cond_wait. */ - static volatile int recv_thread_is_waiting = 0; #endif @@ -117,7 +117,8 @@ void dlq_init (void) dw_printf ("dlq_init ( )\n"); #endif - queue_head = NULL; + queue_head = queue_tail = NULL; + queue_length = 0; #if DEBUG @@ -129,13 +130,6 @@ void dlq_init (void) InitializeCriticalSection (&dlq_cs); #else int err; - err = pthread_mutex_init (&wake_up_mutex, NULL); - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("dlq_init: pthread_mutex_init err=%d", err); - perror (""); - exit (EXIT_FAILURE); - } err = pthread_mutex_init (&dlq_mutex, NULL); if (err != 0) { text_color_set(DW_COLOR_ERROR); @@ -314,9 +308,6 @@ void dlq_rec_frame (int chan, int subchan, int slice, packet_t pp, alevel_t alev static void append_to_queue (struct dlq_item_s *pnew) { - struct dlq_item_s *plast; - int queue_length = 0; - if ( ! was_init) { dlq_init (); } @@ -341,30 +332,19 @@ static void append_to_queue (struct dlq_item_s *pnew) #endif if (queue_head == NULL) { - queue_head = pnew; + assert (queue_tail == NULL); + queue_head = queue_tail = pnew; queue_length = 1; + } else { + assert (queue_tail != NULL); + queue_tail->nextp = pnew; + queue_tail = pnew; + queue_length++; + assert (queue_length > 1); } - else { - queue_length = 2; /* head + new one */ - plast = queue_head; - while (plast->nextp != NULL) { - plast = plast->nextp; - queue_length++; - } - plast->nextp = pnew; - } - #if __WIN32__ LeaveCriticalSection (&dlq_cs); -#else - err = pthread_mutex_unlock (&dlq_mutex); - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("dlq append_to_queue: pthread_mutex_unlock err=%d", err); - perror (""); - exit (1); - } #endif #if DEBUG1 text_color_set(DW_COLOR_DEBUG); @@ -416,7 +396,7 @@ static void append_to_queue (struct dlq_item_s *pnew) * and blocking on a write. */ - if (queue_length > 10) { + if (queue_length > 15) { text_color_set(DW_COLOR_ERROR); dw_printf ("Received frame queue is out of control. Length=%d.\n", queue_length); dw_printf ("Reader thread is probably frozen.\n"); @@ -431,14 +411,6 @@ static void append_to_queue (struct dlq_item_s *pnew) #else if (recv_thread_is_waiting) { - err = pthread_mutex_lock (&wake_up_mutex); - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("dlq append_to_queue: pthread_mutex_lock wu err=%d", err); - perror (""); - exit (1); - } - err = pthread_cond_signal (&wake_up_cond); if (err != 0) { text_color_set(DW_COLOR_ERROR); @@ -446,14 +418,14 @@ static void append_to_queue (struct dlq_item_s *pnew) perror (""); exit (1); } + } - err = pthread_mutex_unlock (&wake_up_mutex); - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("dlq append_to_queue: pthread_mutex_unlock wu err=%d", err); - perror (""); - exit (1); - } + err = pthread_mutex_unlock (&dlq_mutex); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("dlq append_to_queue: pthread_mutex_unlock wu err=%d", err); + perror (""); + exit (1); } #endif @@ -1011,9 +983,29 @@ int dlq_wait_while_empty (double timeout) dlq_init (); } +#if DEBUG1 + text_color_set(DW_COLOR_DEBUG); + dw_printf ("dlq dlq_wait_while_empty: enter critical section\n"); +#endif +#if __WIN32__ + EnterCriticalSection (&dlq_cs); +#else + int err; + err = pthread_mutex_lock (&dlq_mutex); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("dlq append_to_queue: pthread_mutex_lock err=%d", err); + perror (""); + exit (1); + } +#endif if (queue_head == NULL) { +#if __WIN32__ + LeaveCriticalSection (&dlq_cs); +#endif + #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("dlq_wait_while_empty (): prepare to SLEEP...\n"); @@ -1037,18 +1029,15 @@ int dlq_wait_while_empty (double timeout) else { WaitForSingleObject (wake_up_event, INFINITE); } + } else { +#if __WIN32__ + LeaveCriticalSection (&dlq_cs); +#endif + } #else int err; - err = pthread_mutex_lock (&wake_up_mutex); - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("dlq_wait_while_empty: pthread_mutex_lock wu err=%d", err); - perror (""); - exit (1); - } - recv_thread_is_waiting = 1; if (timeout != 0.0) { struct timespec abstime; @@ -1056,26 +1045,25 @@ int dlq_wait_while_empty (double timeout) abstime.tv_sec = (time_t)(long)timeout; abstime.tv_nsec = (long)((timeout - (long)abstime.tv_sec) * 1000000000.0); - err = pthread_cond_timedwait (&wake_up_cond, &wake_up_mutex, &abstime); + err = pthread_cond_timedwait (&wake_up_cond, &dlq_mutex, &abstime); if (err == ETIMEDOUT) { timed_out_result = 1; } } else { - err = pthread_cond_wait (&wake_up_cond, &wake_up_mutex); + err = pthread_cond_wait (&wake_up_cond, &dlq_mutex); } recv_thread_is_waiting = 0; - - err = pthread_mutex_unlock (&wake_up_mutex); - if (err != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("dlq_wait_while_empty: pthread_mutex_unlock wu err=%d", err); - perror (""); - exit (1); - } -#endif } + err = pthread_mutex_unlock (&dlq_mutex); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("dlq_wait_while_empty: pthread_mutex_unlock wu err=%d", err); + perror (""); + exit (1); + } +#endif #if DEBUG text_color_set(DW_COLOR_DEBUG); @@ -1133,6 +1121,14 @@ struct dlq_item_s *dlq_remove (void) if (queue_head != NULL) { result = queue_head; queue_head = queue_head->nextp; + queue_length--; + if (queue_head == NULL) { + assert (queue_length == 0); + queue_tail = NULL; + } + if (queue_length == 1) { + assert (queue_head == queue_tail); + } } #if __WIN32__ diff --git a/src/ptt.c b/src/ptt.c index 5187f1df..a75cb8de 100644 --- a/src/ptt.c +++ b/src/ptt.c @@ -162,6 +162,10 @@ #include #endif +#ifdef USE_GPIOD +#include +#endif + /* So we can have more common code for fd. */ typedef int HANDLE; #define INVALID_HANDLE_VALUE (-1) @@ -468,6 +472,20 @@ void export_gpio(int ch, int ot, int invert, int direction) text_color_set(DW_COLOR_ERROR); dw_printf ("Error writing \"%s\" to %s, errno=%d\n", stemp, gpio_export_path, e); dw_printf ("%s\n", strerror(e)); + + if (e == 22) { + // It appears that error 22 occurs when sysfs gpio is not available. + // (See https://github.com/wb2osz/direwolf/issues/503) + // + // The solution might be to use the new gpiod approach. + + dw_printf ("It looks like gpio with sysfs is not supported on this operating system.\n"); + dw_printf ("Rather than the following form, in the configuration file,\n"); + dw_printf (" PTT GPIO %s\n", stemp); + dw_printf ("try using gpiod form instead. e.g.\n"); + dw_printf (" PTT GPIOD gpiochip0 %s\n", stemp); + dw_printf ("You can get a list of gpio chip names and corresponding I/O lines with \"gpioinfo\" command.\n"); + } exit (1); } } @@ -634,6 +652,31 @@ void export_gpio(int ch, int ot, int invert, int direction) get_access_to_gpio (gpio_value_path); } +#if defined(USE_GPIOD) +int gpiod_probe(const char *chip_name, int line_number) +{ + struct gpiod_chip *chip; + chip = gpiod_chip_open_by_name(chip_name); + if (chip == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Can't open GPIOD chip %s.\n", chip_name); + return -1; + } + + struct gpiod_line *line; + line = gpiod_chip_get_line(chip, line_number); + if (line == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Can't get GPIOD line %d.\n", line_number); + return -1; + } + if (ptt_debug_level >= 2) { + text_color_set(DW_COLOR_DEBUG); + dw_printf("GPIOD probe OK. Chip: %s line: %d\n", chip_name, line_number); + } + return 0; +} +#endif /* USE_GPIOD */ #endif /* not __WIN32__ */ @@ -650,7 +693,8 @@ void export_gpio(int ch, int ot, int invert, int direction) * ptt_method Method for PTT signal. * PTT_METHOD_NONE - not configured. Could be using VOX. * PTT_METHOD_SERIAL - serial (com) port. - * PTT_METHOD_GPIO - general purpose I/O. + * PTT_METHOD_GPIO - general purpose I/O (sysfs). + * PTT_METHOD_GPIOD - general purpose I/O (libgpiod). * PTT_METHOD_LPT - Parallel printer port. * PTT_METHOD_HAMLIB - HAMLib rig control. * PTT_METHOD_CM108 - GPIO pins of CM108 etc. USB Audio. @@ -729,12 +773,13 @@ void ptt_init (struct audio_s *audio_config_p) if (ptt_debug_level >= 2) { text_color_set(DW_COLOR_DEBUG); - dw_printf ("ch=%d, %s method=%d, device=%s, line=%d, gpio=%d, lpt_bit=%d, invert=%d\n", + dw_printf ("ch=%d, %s method=%d, device=%s, line=%d, name=%s, gpio=%d, lpt_bit=%d, invert=%d\n", ch, otnames[ot], audio_config_p->achan[ch].octrl[ot].ptt_method, audio_config_p->achan[ch].octrl[ot].ptt_device, audio_config_p->achan[ch].octrl[ot].ptt_line, + audio_config_p->achan[ch].octrl[ot].out_gpio_name, audio_config_p->achan[ch].octrl[ot].out_gpio_num, audio_config_p->achan[ch].octrl[ot].ptt_lpt_bit, audio_config_p->achan[ch].octrl[ot].ptt_invert); @@ -880,7 +925,28 @@ void ptt_init (struct audio_s *audio_config_p) if (using_gpio) { get_access_to_gpio ("/sys/class/gpio/export"); } - +#if defined(USE_GPIOD) + // GPIOD + for (ch = 0; ch < MAX_CHANS; ch++) { + if (save_audio_config_p->chan_medium[ch] == MEDIUM_RADIO) { + for (int ot = 0; ot < NUM_OCTYPES; ot++) { + if (audio_config_p->achan[ch].octrl[ot].ptt_method == PTT_METHOD_GPIOD) { + const char *chip_name = audio_config_p->achan[ch].octrl[ot].out_gpio_name; + int line_number = audio_config_p->achan[ch].octrl[ot].out_gpio_num; + int rc = gpiod_probe(chip_name, line_number); + if (rc < 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Disable PTT for channel %d\n", ch); + audio_config_p->achan[ch].octrl[ot].ptt_method = PTT_METHOD_NONE; + } else { + // Set initial state off ptt_set will invert output signal if appropriate. + ptt_set (ot, ch, 0); + } + } + } + } + } +#endif /* USE_GPIOD */ /* * We should now be able to create the device nodes for * the pins we want to use. @@ -1298,6 +1364,18 @@ void ptt_set (int ot, int chan, int ptt_signal) close (fd); } + +#if defined(USE_GPIOD) + if (save_audio_config_p->achan[chan].octrl[ot].ptt_method == PTT_METHOD_GPIOD) { + const char *chip = save_audio_config_p->achan[chan].octrl[ot].out_gpio_name; + int line = save_audio_config_p->achan[chan].octrl[ot].out_gpio_num; + int rc = gpiod_ctxless_set_value(chip, line, ptt, false, "direwolf", NULL, NULL); + if (ptt_debug_level >= 1) { + text_color_set(DW_COLOR_DEBUG); + dw_printf("PTT_METHOD_GPIOD chip: %s line: %d ptt: %d rc: %d\n", chip, line, ptt, rc); + } + } +#endif /* USE_GPIOD */ #endif /*