nuttx-apps/system/cu/cu_main.c
Xiang Xiao a5ef6d57c1 system/cu: Remove the dependence on stdio FILE * function
Signed-off-by: Xiang Xiao <xiaoxiang@xiaomi.com>
2023-09-06 14:29:34 +02:00

504 lines
12 KiB
C

/****************************************************************************
* apps/system/cu/cu_main.c
*
* Copyright (C) 2014 sysmocom - s.f.m.c. GmbH. All rights reserved.
* Author: Harald Welte <hwelte@sysmocom.de>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name NuttX nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <signal.h>
#include <debug.h>
#include "system/readline.h"
#include "cu.h"
#ifdef CONFIG_SYSTEM_CUTERM_DISABLE_ERROR_PRINT
# define cu_error(...)
#else
# define cu_error(...) dprintf(STDERR_FILENO, __VA_ARGS__)
#endif
/****************************************************************************
* Private Types
****************************************************************************/
enum parity_mode
{
PARITY_NONE,
PARITY_EVEN,
PARITY_ODD,
};
/****************************************************************************
* Private Data
****************************************************************************/
static struct cu_globals_s g_cu;
/****************************************************************************
* Public Data
****************************************************************************/
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: cu_listener
*
* Description:
* Entry point for the listener thread.
*
****************************************************************************/
static FAR void *cu_listener(FAR void *parameter)
{
FAR struct cu_globals_s *cu = (FAR struct cu_globals_s *)parameter;
for (; ; )
{
int rc;
char ch;
rc = read(cu->devfd, &ch, 1);
if (rc <= 0)
{
break;
}
write(STDOUT_FILENO, &ch, 1);
}
/* Won't get here */
return NULL;
}
static void sigint(int sig)
{
g_cu.force_exit = true;
}
#ifdef CONFIG_SERIAL_TERMIOS
static int set_termios(FAR struct cu_globals_s *cu, int rate,
enum parity_mode parity, int rtscts, int nocrlf)
#else
static int set_termios(FAR struct cu_globals_s *cu, int nocrlf)
#endif
{
int ret;
struct termios tio;
if (isatty(cu->devfd))
{
tio = cu->devtio;
#ifdef CONFIG_SERIAL_TERMIOS
tio.c_cflag &= ~(PARENB | PARODD | CRTSCTS);
switch (parity)
{
case PARITY_EVEN:
tio.c_cflag |= PARENB;
break;
case PARITY_ODD:
tio.c_cflag |= PARENB | PARODD;
break;
case PARITY_NONE:
break;
}
/* Set baudrate */
if (rate != 0)
{
cfsetspeed(&tio, rate);
}
if (rtscts)
{
tio.c_cflag |= CRTS_IFLOW | CCTS_OFLOW;
}
#endif
tio.c_oflag = OPOST;
/* Enable or disable \n -> \r\n conversion during write */
if (nocrlf == 0)
{
tio.c_oflag |= ONLCR;
}
ret = tcsetattr(cu->devfd, TCSANOW, &tio);
if (ret)
{
cu_error("set_termios: ERROR during tcsetattr(): %d\n", errno);
return ret;
}
}
/* Let the remote machine to handle all crlf/echo except Ctrl-C */
if (cu->stdfd >= 0)
{
tio = cu->stdtio;
tio.c_iflag = 0;
tio.c_oflag = 0;
tio.c_lflag &= ~ECHO;
ret = tcsetattr(cu->stdfd, TCSANOW, &tio);
if (ret)
{
cu_error("set_termios: ERROR during tcsetattr(): %d\n", errno);
return ret;
}
}
return 0;
}
static void retrieve_termios(FAR struct cu_globals_s *cu)
{
if (isatty(cu->devfd))
{
tcsetattr(cu->devfd, TCSANOW, &cu->devtio);
}
if (cu->stdfd >= 0)
{
tcsetattr(cu->stdfd, TCSANOW, &cu->stdtio);
}
}
static void print_help(void)
{
printf("Usage: cu [options]\n"
" -l: Use named device (default %s)\n"
#ifdef CONFIG_SERIAL_TERMIOS
" -e: Set even parity\n"
" -o: Set odd parity\n"
" -s: Use given speed (default %d)\n"
" -f: Disable RTS/CTS flow control (default: on)\n"
#endif
" -c: Disable lf -> crlf conversion (default: off)\n"
" -E: Set the escape character (default: ~).\n"
" To eliminate the escape character, use -E ''\n"
" -?: This help\n",
CONFIG_SYSTEM_CUTERM_DEFAULT_DEVICE
#ifdef CONFIG_SERIAL_TERMIOS
, CONFIG_SYSTEM_CUTERM_DEFAULT_BAUD
#endif
);
}
static void print_escape_help(FAR struct cu_globals_s *cu)
{
printf("[Escape sequences]\n[%c. hangup]\n", cu->escape);
}
static int cu_cmd(FAR struct cu_globals_s *cu, char bcmd)
{
switch (bcmd)
{
case '?':
print_escape_help(cu);
break;
case '.':
return 1;
/* FIXME: implement other commands such as send/receive file */
}
return 0;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: cu_main
*
* Description:
* Main entry point for the serial terminal example.
*
****************************************************************************/
int main(int argc, FAR char *argv[])
{
pthread_attr_t attr;
struct sigaction sa;
FAR const char *devname = CONFIG_SYSTEM_CUTERM_DEFAULT_DEVICE;
FAR struct cu_globals_s *cu = &g_cu;
#ifdef CONFIG_SERIAL_TERMIOS
int baudrate = CONFIG_SYSTEM_CUTERM_DEFAULT_BAUD;
enum parity_mode parity = PARITY_NONE;
int rtscts = 1;
#endif
int nocrlf = 0;
int option;
int ret;
int start_of_line = 1;
int exitval = EXIT_FAILURE;
bool badarg = false;
/* Initialize global data */
memset(cu, 0, sizeof(*cu));
cu->escape = '~';
/* Install signal handlers */
memset(&sa, 0, sizeof(sa));
sa.sa_handler = sigint;
sigaction(SIGINT, &sa, NULL);
optind = 0; /* Global that needs to be reset in FLAT mode */
while ((option = getopt(argc, argv, "l:s:ceE:fho?")) != ERROR)
{
switch (option)
{
case 'l':
devname = optarg;
break;
#ifdef CONFIG_SERIAL_TERMIOS
case 's':
baudrate = atoi(optarg);
break;
case 'e':
parity = PARITY_EVEN;
break;
case 'o':
parity = PARITY_ODD;
break;
case 'f':
rtscts = 0;
break;
#endif
case 'c':
nocrlf = 1;
break;
case 'E':
cu->escape = atoi(optarg);
break;
case 'h':
case '?':
print_help();
exitval = EXIT_SUCCESS;
/* Go through */
default:
badarg = true;
break;
}
}
if (badarg)
{
return exitval;
}
/* Open the serial device for reading and writing */
cu->devfd = open(devname, O_RDWR);
if (cu->devfd < 0)
{
cu_error("cu_main: ERROR: Failed to open %s for writing: %d\n",
devname, errno);
goto errout_with_devinit;
}
/* Remember serial device termios attributes */
if (isatty(cu->devfd))
{
ret = tcgetattr(cu->devfd, &cu->devtio);
if (ret)
{
cu_error("cu_main: ERROR during tcgetattr(): %d\n", errno);
goto errout_with_devfd;
}
}
/* Remember std termios attributes if it is a tty. Try to select
* right descriptor that is used to refer to tty
*/
if (isatty(STDERR_FILENO))
{
cu->stdfd = STDERR_FILENO;
}
else if (isatty(STDOUT_FILENO))
{
cu->stdfd = STDOUT_FILENO;
}
else if (isatty(STDIN_FILENO))
{
cu->stdfd = STDIN_FILENO;
}
else
{
cu->stdfd = -1;
}
if (cu->stdfd >= 0)
{
tcgetattr(cu->stdfd, &cu->stdtio);
}
#ifdef CONFIG_SERIAL_TERMIOS
if (set_termios(cu, baudrate, parity, rtscts, nocrlf) != 0)
#else
if (set_termios(cu, nocrlf) != 0)
#endif
{
goto errout_with_devfd_retrieve;
}
/* Start the serial receiver thread */
ret = pthread_attr_init(&attr);
if (ret != OK)
{
cu_error("cu_main: pthread_attr_init failed: %d\n", ret);
goto errout_with_devfd_retrieve;
}
/* Set priority of listener to configured value */
attr.priority = CONFIG_SYSTEM_CUTERM_PRIORITY;
ret = pthread_create(&cu->listener, &attr, cu_listener, cu);
pthread_attr_destroy(&attr);
if (ret != 0)
{
cu_error("cu_main: Error in thread creation: %d\n", ret);
goto errout_with_devfd_retrieve;
}
/* Send messages and get responses -- forever */
while (!cu->force_exit)
{
char ch;
if (read(STDIN_FILENO, &ch, 1) <= 0)
{
continue;
}
if (start_of_line == 1 && ch == cu->escape)
{
/* We've seen and escape (~) character, echo it to local
* terminal and read the next char from serial
*/
write(STDOUT_FILENO, &ch, 1);
if (read(STDIN_FILENO, &ch, 1) <= 0)
{
continue;
}
if (ch == cu->escape)
{
/* Escaping a tilde: handle like normal char */
write(cu->devfd, &ch, 1);
continue;
}
else
{
if (cu_cmd(cu, ch) == 1)
{
break;
}
}
}
/* Normal character */
write(cu->devfd, &ch, 1);
/* Determine if we are now at the start of a new line or not */
if (ch == '\n' || ch == '\r')
{
start_of_line = 1;
}
else
{
start_of_line = 0;
}
}
pthread_cancel(cu->listener);
exitval = EXIT_SUCCESS;
/* Error exits */
errout_with_devfd_retrieve:
retrieve_termios(cu);
errout_with_devfd:
close(cu->devfd);
errout_with_devinit:
return exitval;
}