From 4fef4a381d0b8a47d2230c52613ddc1fedf6e9e0 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Sat, 12 Mar 2022 16:48:31 -0500 Subject: [PATCH] port upstream PseudoPTY support; add systemd service file; update Makefile to support install; --- DVMHost.vcxproj | 2 + DVMHost.vcxproj.filters | 6 + Makefile | 31 ++++- linux/dvmhost.service | 12 ++ modem/port/PseudoPTYPort.cpp | 117 +++++++++++++++++++ modem/port/PseudoPTYPort.h | 72 ++++++++++++ modem/port/UARTPort.cpp | 215 +++++++++++++++++++---------------- modem/port/UARTPort.h | 6 + 8 files changed, 363 insertions(+), 98 deletions(-) create mode 100644 linux/dvmhost.service create mode 100644 modem/port/PseudoPTYPort.cpp create mode 100644 modem/port/PseudoPTYPort.h diff --git a/DVMHost.vcxproj b/DVMHost.vcxproj index 11a50ad2..20fbdd41 100644 --- a/DVMHost.vcxproj +++ b/DVMHost.vcxproj @@ -202,6 +202,7 @@ + @@ -281,6 +282,7 @@ + diff --git a/DVMHost.vcxproj.filters b/DVMHost.vcxproj.filters index 5917e50d..3bf2aa99 100644 --- a/DVMHost.vcxproj.filters +++ b/DVMHost.vcxproj.filters @@ -353,6 +353,9 @@ Header Files\modem\port + + Header Files\modem\port + Header Files\modem\port @@ -571,6 +574,9 @@ Source Files\modem\port + + Source Files\modem\port + Source Files\modem\port diff --git a/Makefile b/Makefile index 2fd40293..8b19eeaa 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ rpi-armSTRIP= /opt/tools/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf CFLAGS = -g -O3 -Wall -std=c++0x -pthread -I. EXTFLAGS= -LIBS = -lpthread +LIBS = -lpthread -lutil LDFLAGS = -g BIN = dvmhost @@ -69,6 +69,7 @@ OBJECTS = \ modem/port/ISerialPort.o \ modem/port/ModemNullPort.o \ modem/port/UARTPort.o \ + modem/port/PseudoPTYPort.o \ modem/port/UDPPort.o \ modem/Modem.o \ network/UDPSocket.o \ @@ -98,3 +99,31 @@ strip: clean: $(RM) $(BIN) $(OBJECTS) *.o *.d *.bak *~ +install: all + @mkdir -p /opt/dvm + install -m 755 $(BIN) /opt/dvm/bin/ + +install-config-files: + @mkdir -p /opt/dvm + @cp -n config.example.yml /opt/dvm/config.yml + @cp -n iden_table.example.dat /opt/dvm/iden_table.dat + @cp -n rid_acl.example.dat /opt/dvm/rid_acl.dat + @cp -n tg_acl.example.dat /opt/dvm/tg_acl.dat + @sed -i 's/filePath: ./filePath: \/opt\/dvm\/log\//' /opt/dvm/config.yml + @sed -i 's/activityFilePath: ./activityFilePath: \/opt\/dvm\/log\//' /opt/dvm/config.yml + @sed -i 's/file: iden_table.dat/file: \/opt\/dvm\/iden_table.dat/' /opt/dvm/config.yml + @sed -i 's/file: rid_acl.dat/file: \/opt\/dvm\/rid_acl.dat/' /opt/dvm/config.yml + @sed -i 's/file: tg_acl.dat/file: \/opt\/dvm\/tg_acl.dat/' /opt/dvm/config.yml + +install-service: install install-config-files + @useradd --user-group -M --system dvmhost --shell /bin/false || true + @usermod --groups dialout --append dvmhost || true + @mkdir /opt/dvm/log || true + @chown dvmhost:dvmhost /opt/dvm/log + @cp ./linux/dvmhost.service /lib/systemd/system/ + @systemctl enable dvmhost.service + +uninstall-service: + @systemctl stop dvmhost.service || true + @systemctl disable dvmhost.service || true + @rm -f /lib/systemd/system/dvmhost.service || true diff --git a/linux/dvmhost.service b/linux/dvmhost.service new file mode 100644 index 00000000..2963d010 --- /dev/null +++ b/linux/dvmhost.service @@ -0,0 +1,12 @@ +[Unit] +Description=DVMProject Host Radio Service +After=syslog.target network.target + +[Service] +User=mmdvm +Type=forking +ExecStart=/opt/dvm/bin/dvmhost -c /opt/dvm/config.yml +Restart=on-abnormal + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/modem/port/PseudoPTYPort.cpp b/modem/port/PseudoPTYPort.cpp new file mode 100644 index 00000000..8bc00f6f --- /dev/null +++ b/modem/port/PseudoPTYPort.cpp @@ -0,0 +1,117 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* + * Copyright (C) 2020,2021 by Jonathan Naylor G4KLX + * + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include "modem/port/PseudoPTYPort.h" +#include "Log.h" + +#include +#include +#include + +using namespace modem::port; + +#if !defined(_WIN32) && !defined(_WIN64) + +#include +#include +#include +#include +#include +#include +#include +#if defined(__linux__) + #include +#else + #include +#endif + +// --------------------------------------------------------------------------- +// Public Class Members +// --------------------------------------------------------------------------- +/// +/// Initializes a new instance of the PseudoPTYPort class. +/// +/// Serial port device. +/// Serial port speed. +/// +PseudoPTYPort::PseudoPTYPort(const std::string& symlink, SERIAL_SPEED speed, bool assertRTS) : UARTPort(speed, assertRTS), + m_symlink(symlink) +{ + /* stub */ +} + +/// +/// Finalizes a instance of the PseudoPTYPort class. +/// +PseudoPTYPort::~PseudoPTYPort() +{ + /* stub */ +} + +/// +/// Opens a connection to the serial port. +/// +/// True, if connection is opened, otherwise false. +bool PseudoPTYPort::open() +{ + assert(m_fd == -1); + + int slavefd; + char slave[300]; + int result = ::openpty(&m_fd, &slavefd, slave, NULL, NULL); + if (result < 0) { + ::LogError(LOG_HOST, "Cannot open the pseudo tty - errno : %d", errno); + return false; + } + + // remove any previous stale symlink + ::unlink(m_symlink.c_str()); + + int ret = ::symlink(slave, m_symlink.c_str()); + if (ret != 0) { + ::LogError(LOG_HOST, "Cannot make symlink to %s with %s", slave, m_symlink.c_str()); + close(); + return false; + } + + ::LogMessage(LOG_HOST, "Made symbolic link from %s to %s", slave, m_symlink.c_str()); + m_device = std::string(::ttyname(m_fd)); + return setTermios(); +} + +/// +/// Closes the connection to the serial port. +/// +void PseudoPTYPort::close() +{ + UARTPort::close(); + ::unlink(m_symlink.c_str()); +} + +#endif diff --git a/modem/port/PseudoPTYPort.h b/modem/port/PseudoPTYPort.h new file mode 100644 index 00000000..ddf127e4 --- /dev/null +++ b/modem/port/PseudoPTYPort.h @@ -0,0 +1,72 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Software +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* + * Copyright (C) 2020,2021 by Jonathan Naylor G4KLX + * + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#if !defined(__PSEUDO_PTY_PORT_H__) +#define __PSEUDO_PTY_PORT_H__ + +#if !defined(_WIN32) && !defined(_WIN64) + +#include "Defines.h" +#include "modem/port/UARTPort.h" + +#include + +namespace modem +{ + namespace port + { + // --------------------------------------------------------------------------- + // Class Declaration + // This class implements low-level routines to communicate via a Linux + // PTY serial port. + // --------------------------------------------------------------------------- + + class HOST_SW_API PseudoPTYPort : public UARTPort + { + public: + /// Initializes a new instance of the PseudoPTYPort class. + PseudoPTYPort(const std::string& symlink, SERIAL_SPEED speed, bool assertRTS = false); + /// Finalizes a instance of the PseudoPTYPort class. + virtual ~PseudoPTYPort(); + + /// Opens a connection to the serial port. + virtual bool open(); + + /// Closes the connection to the serial port. + virtual void close(); + + protected: + std::string m_symlink; + }; // class HOST_SW_API PseudoPTYPort : public UARTPort + } // namespace port +} // namespace Modem + +#endif + +#endif // __PSEUDO_PTY_PORT_H__ diff --git a/modem/port/UARTPort.cpp b/modem/port/UARTPort.cpp index 6e39bfcf..ce2465aa 100644 --- a/modem/port/UARTPort.cpp +++ b/modem/port/UARTPort.cpp @@ -258,7 +258,7 @@ void UARTPort::close() /// Serial port device. /// Serial port speed. /// -UARTPort::UARTPort(const std::string& device, SERIAL_SPEED speed, bool assertRTS) : +UARTPort::UARTPort(const std::string& device, SERIAL_SPEED speed, bool assertRTS) : m_isOpen(false), m_device(device), m_speed(speed), @@ -302,101 +302,7 @@ bool UARTPort::open() return false; } - termios termios; - if (::tcgetattr(m_fd, &termios) < 0) { - ::LogError(LOG_HOST, "Cannot get the attributes for %s", m_device.c_str()); - ::close(m_fd); - return false; - } - - termios.c_iflag &= ~(IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK); - termios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL); - termios.c_iflag &= ~(IXON | IXOFF | IXANY); - termios.c_oflag &= ~(OPOST); - termios.c_cflag &= ~(CSIZE | CSTOPB | PARENB | CRTSCTS); - termios.c_cflag |= (CS8 | CLOCAL | CREAD); - termios.c_lflag &= ~(ISIG | ICANON | IEXTEN); - termios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); -#if defined(__APPLE__) - termios.c_cc[VMIN] = 1; - termios.c_cc[VTIME] = 1; -#else - termios.c_cc[VMIN] = 0; - termios.c_cc[VTIME] = 10; -#endif - - switch (m_speed) { - case SERIAL_1200: - ::cfsetospeed(&termios, B1200); - ::cfsetispeed(&termios, B1200); - break; - case SERIAL_2400: - ::cfsetospeed(&termios, B2400); - ::cfsetispeed(&termios, B2400); - break; - case SERIAL_4800: - ::cfsetospeed(&termios, B4800); - ::cfsetispeed(&termios, B4800); - break; - case SERIAL_9600: - ::cfsetospeed(&termios, B9600); - ::cfsetispeed(&termios, B9600); - break; - case SERIAL_19200: - ::cfsetospeed(&termios, B19200); - ::cfsetispeed(&termios, B19200); - break; - case SERIAL_38400: - ::cfsetospeed(&termios, B38400); - ::cfsetispeed(&termios, B38400); - break; - case SERIAL_115200: - ::cfsetospeed(&termios, B115200); - ::cfsetispeed(&termios, B115200); - break; - case SERIAL_230400: - ::cfsetospeed(&termios, B230400); - ::cfsetispeed(&termios, B230400); - break; - case SERIAL_460800: - ::cfsetospeed(&termios, B460800); - ::cfsetispeed(&termios, B460800); - break; - default: - ::LogError(LOG_HOST, "Unsupported serial port speed - %u", m_speed); - ::close(m_fd); - return false; - } - - if (::tcsetattr(m_fd, TCSANOW, &termios) < 0) { - ::LogError(LOG_HOST, "Cannot set the attributes for %s", m_device.c_str()); - ::close(m_fd); - return false; - } - - if (m_assertRTS) { - uint32_t y; - if (::ioctl(m_fd, TIOCMGET, &y) < 0) { - ::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_device.c_str()); - ::close(m_fd); - return false; - } - - y |= TIOCM_RTS; - - if (::ioctl(m_fd, TIOCMSET, &y) < 0) { - ::LogError(LOG_HOST, "Cannot set the control attributes for %s", m_device.c_str()); - ::close(m_fd); - return false; - } - } - -#if defined(__APPLE__) - setNonblock(false); -#endif - - m_isOpen = true; - return true; + return setTermios(); } /// @@ -515,7 +421,7 @@ void UARTPort::close() /// int UARTPort::setNonblock(bool nonblock) { - int flag = ::fcntl(m_fd, F_GETFD, 0); + int flag = ::fcntl(m_fd, F_GETFL, 0); if (nonblock) flag |= O_NONBLOCK; @@ -530,6 +436,18 @@ int UARTPort::setNonblock(bool nonblock) // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- +UARTPort::UARTPort(SERIAL_SPEED speed, bool assertRTS) : + m_isOpen(false), + m_speed(speed), + m_assertRTS(assertRTS), +#if defined(_WIN32) || defined(_WIN64) + m_handle(INVALID_HANDLE_VALUE) +#else + m_fd(-1) +#endif +{ + /* stub */ +} #if defined(_WIN32) || defined(_WIN64) /// @@ -596,4 +514,107 @@ bool UARTPort::canWrite() return true; #endif } + +/// +/// +/// +/// +bool UARTPort::setTermios() +{ + termios termios; + if (::tcgetattr(m_fd, &termios) < 0) { + ::LogError(LOG_HOST, "Cannot get the attributes for %s", m_device.c_str()); + ::close(m_fd); + return false; + } + + termios.c_iflag &= ~(IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK); + termios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL); + termios.c_iflag &= ~(IXON | IXOFF | IXANY); + termios.c_oflag &= ~(OPOST); + termios.c_cflag &= ~(CSIZE | CSTOPB | PARENB | CRTSCTS); + termios.c_cflag |= (CS8 | CLOCAL | CREAD); + termios.c_lflag &= ~(ISIG | ICANON | IEXTEN); + termios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); +#if defined(__APPLE__) + termios.c_cc[VMIN] = 1; + termios.c_cc[VTIME] = 1; +#else + termios.c_cc[VMIN] = 0; + termios.c_cc[VTIME] = 10; +#endif + + switch (m_speed) { + case SERIAL_1200: + ::cfsetospeed(&termios, B1200); + ::cfsetispeed(&termios, B1200); + break; + case SERIAL_2400: + ::cfsetospeed(&termios, B2400); + ::cfsetispeed(&termios, B2400); + break; + case SERIAL_4800: + ::cfsetospeed(&termios, B4800); + ::cfsetispeed(&termios, B4800); + break; + case SERIAL_9600: + ::cfsetospeed(&termios, B9600); + ::cfsetispeed(&termios, B9600); + break; + case SERIAL_19200: + ::cfsetospeed(&termios, B19200); + ::cfsetispeed(&termios, B19200); + break; + case SERIAL_38400: + ::cfsetospeed(&termios, B38400); + ::cfsetispeed(&termios, B38400); + break; + case SERIAL_115200: + ::cfsetospeed(&termios, B115200); + ::cfsetispeed(&termios, B115200); + break; + case SERIAL_230400: + ::cfsetospeed(&termios, B230400); + ::cfsetispeed(&termios, B230400); + break; + case SERIAL_460800: + ::cfsetospeed(&termios, B460800); + ::cfsetispeed(&termios, B460800); + break; + default: + ::LogError(LOG_HOST, "Unsupported serial port speed - %u", m_speed); + ::close(m_fd); + return false; + } + + if (::tcsetattr(m_fd, TCSANOW, &termios) < 0) { + ::LogError(LOG_HOST, "Cannot set the attributes for %s", m_device.c_str()); + ::close(m_fd); + return false; + } + + if (m_assertRTS) { + uint32_t y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot get the control attributes for %s", m_device.c_str()); + ::close(m_fd); + return false; + } + + y |= TIOCM_RTS; + + if (::ioctl(m_fd, TIOCMSET, &y) < 0) { + ::LogError(LOG_HOST, "Cannot set the control attributes for %s", m_device.c_str()); + ::close(m_fd); + return false; + } + } + +#if defined(__APPLE__) + setNonblock(false); +#endif + + m_isOpen = true; + return true; +} #endif diff --git a/modem/port/UARTPort.h b/modem/port/UARTPort.h index c8c4f266..81159902 100644 --- a/modem/port/UARTPort.h +++ b/modem/port/UARTPort.h @@ -94,6 +94,9 @@ namespace modem #endif protected: + /// Initializes a new instance of the UARTPort class. + UARTPort(SERIAL_SPEED speed, bool assertRTS = false); + bool m_isOpen; std::string m_device; @@ -111,6 +114,9 @@ namespace modem #else /// bool canWrite(); + + /// + bool setTermios(); #endif }; // class HOST_SW_API UARTPort : public ISerialPort, public IModemPort } // namespace port