Rename the driver from uart_native_posix to uart_native_pty. Including renaming the DTS compatible, and kconfig options, deprecating the old ones. And refactor the driver, generalizing it, so we can have any number of instances. Note that, unfortunately generalizing to N instances cannot be done without a degree of backwards compatibility breakage: This driver was born with all its configuration and selection of the instances based on kconfig. When the driver was made to use DT, it was done in a way that required both DT and kconfig needing to manually coherently enable the 2nd UART. This has now been fixed, which it means only DT is used to decide how many instances are avaliable, and UART_NATIVE_POSIX_PORT_1_ENABLE is just ignored. Including: * Deprecate UART_NATIVE_WAIT_PTS_READY_ENABLE: the options is always on now as it has no practical drawbacks. * Deprecate UART_NATIVE_POSIX_PORT_1_ENABLE: DTS intanciation defines it being available now. * Rename a few functions and in general shorten pseudo-tty/pseudo- terminal to PTY instead of PTTY. Signed-off-by: Alberto Escolar Piedras <alberto.escolar.piedras@nordicsemi.no>
249 lines
6.0 KiB
C
249 lines
6.0 KiB
C
/*
|
|
* Copyright (c) 2018, Oticon A/S
|
|
* Copyright (c) 2023, Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#undef _XOPEN_SOURCE
|
|
/* Note: This is used only for interaction with the host C library, and is therefore exempt of
|
|
* coding guidelines rule A.4&5 which applies to the embedded code using embedded libraries
|
|
*/
|
|
#define _XOPEN_SOURCE 600
|
|
|
|
#include <stdbool.h>
|
|
#include <errno.h>
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <pty.h>
|
|
#include <fcntl.h>
|
|
#include <sys/select.h>
|
|
#include <unistd.h>
|
|
#include <poll.h>
|
|
#include <nsi_tracing.h>
|
|
|
|
#define ERROR nsi_print_error_and_exit
|
|
#define WARN nsi_print_warning
|
|
|
|
/**
|
|
* @brief Poll the device for input.
|
|
*
|
|
* @param in_f Input file descriptor
|
|
* @param p_char Pointer to character.
|
|
*
|
|
* @retval 0 If a character arrived and was stored in p_char
|
|
* @retval -1 If no character was available to read
|
|
* @retval -2 if the stdin is disconnected
|
|
*/
|
|
int np_uart_stdin_poll_in_bottom(int in_f, unsigned char *p_char)
|
|
{
|
|
if (feof(stdin)) {
|
|
/*
|
|
* The stdinput is fed from a file which finished or the user
|
|
* pressed Ctrl+D
|
|
*/
|
|
return -2;
|
|
}
|
|
|
|
int n = -1;
|
|
|
|
int ready;
|
|
fd_set readfds;
|
|
static struct timeval timeout; /* just zero */
|
|
|
|
FD_ZERO(&readfds);
|
|
FD_SET(in_f, &readfds);
|
|
|
|
ready = select(in_f+1, &readfds, NULL, NULL, &timeout);
|
|
|
|
if (ready == 0) {
|
|
return -1;
|
|
} else if (ready == -1) {
|
|
ERROR("%s: Error on select ()\n", __func__);
|
|
}
|
|
|
|
n = read(in_f, p_char, 1);
|
|
if ((n == -1) || (n == 0)) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Check if the output descriptor has something connected to the slave side
|
|
*
|
|
* @param fd file number
|
|
*
|
|
* @retval 0 Nothing connected yet
|
|
* @retval 1 Something connected to the slave side
|
|
*/
|
|
int np_uart_slave_connected(int fd)
|
|
{
|
|
struct pollfd pfd = { .fd = fd, .events = POLLHUP };
|
|
int ret;
|
|
|
|
ret = poll(&pfd, 1, 0);
|
|
if (ret == -1) {
|
|
int err = errno;
|
|
/*
|
|
* Possible errors are:
|
|
* * EINTR :A signal was received => ok
|
|
* * EFAULT and EINVAL: parameters/programming error
|
|
* * ENOMEM no RAM left
|
|
*/
|
|
if (err != EINTR) {
|
|
ERROR("%s: unexpected error during poll, errno=%i,%s\n",
|
|
__func__, err, strerror(err));
|
|
}
|
|
}
|
|
if (!(pfd.revents & POLLHUP)) {
|
|
/* There is now a reader on the slave side */
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Attempt to connect a terminal emulator to the slave side of the pty
|
|
* If -attach_uart_cmd=<cmd> is provided as a command line option, <cmd> will be
|
|
* used. Otherwise, the default command,
|
|
* CONFIG_NATIVE_UART_AUTOATTACH_DEFAULT_CMD, will be used instead
|
|
*/
|
|
static void attach_to_pty(const char *slave_pty, const char *auto_attach_cmd)
|
|
{
|
|
char command[strlen(auto_attach_cmd) + strlen(slave_pty) + 1];
|
|
|
|
sprintf(command, auto_attach_cmd, slave_pty);
|
|
|
|
int ret = system(command);
|
|
|
|
if (ret != 0) {
|
|
WARN("Could not attach to the UART with \"%s\"\n", command);
|
|
WARN("The command returned %i\n", WEXITSTATUS(ret));
|
|
}
|
|
}
|
|
/**
|
|
* Attempt to allocate and open a new pseudoterminal
|
|
*
|
|
* Returns the file descriptor of the master side
|
|
* If auto_attach was set, it will also attempt to connect a new terminal
|
|
* emulator to its slave side.
|
|
*/
|
|
int np_uart_open_pty(const char *uart_name, const char *auto_attach_cmd,
|
|
bool do_auto_attach, bool wait_pts)
|
|
{
|
|
int master_pty;
|
|
char *slave_pty_name;
|
|
struct termios ter;
|
|
int err_nbr;
|
|
int ret;
|
|
int flags;
|
|
|
|
master_pty = posix_openpt(O_RDWR | O_NOCTTY);
|
|
if (master_pty == -1) {
|
|
ERROR("Could not open a new PTY for the UART\n");
|
|
}
|
|
ret = grantpt(master_pty);
|
|
if (ret == -1) {
|
|
err_nbr = errno;
|
|
close(master_pty);
|
|
ERROR("Could not grant access to the slave PTY side (%i)\n",
|
|
err_nbr);
|
|
}
|
|
ret = unlockpt(master_pty);
|
|
if (ret == -1) {
|
|
err_nbr = errno;
|
|
close(master_pty);
|
|
ERROR("Could not unlock the slave PTY side (%i)\n", err_nbr);
|
|
}
|
|
slave_pty_name = ptsname(master_pty);
|
|
if (slave_pty_name == NULL) {
|
|
err_nbr = errno;
|
|
close(master_pty);
|
|
ERROR("Error getting slave PTY device name (%i)\n", err_nbr);
|
|
}
|
|
/* Set the master PTY as non blocking */
|
|
flags = fcntl(master_pty, F_GETFL);
|
|
if (flags == -1) {
|
|
err_nbr = errno;
|
|
close(master_pty);
|
|
ERROR("Could not read the master PTY file status flags (%i)\n",
|
|
err_nbr);
|
|
}
|
|
|
|
ret = fcntl(master_pty, F_SETFL, flags | O_NONBLOCK);
|
|
if (ret == -1) {
|
|
err_nbr = errno;
|
|
close(master_pty);
|
|
ERROR("Could not set the master PTY as non-blocking (%i)\n",
|
|
err_nbr);
|
|
}
|
|
|
|
(void) err_nbr;
|
|
|
|
/*
|
|
* Set terminal in "raw" mode:
|
|
* Not canonical (no line input)
|
|
* No signal generation from Ctr+{C|Z..}
|
|
* No echoing, no input or output processing
|
|
* No replacing of NL or CR
|
|
* No flow control
|
|
*/
|
|
ret = tcgetattr(master_pty, &ter);
|
|
if (ret == -1) {
|
|
ERROR("Could not read terminal driver settings\n");
|
|
}
|
|
ter.c_cc[VMIN] = 0;
|
|
ter.c_cc[VTIME] = 0;
|
|
ter.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO);
|
|
ter.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR | INPCK
|
|
| ISTRIP | IXON | PARMRK);
|
|
ter.c_oflag &= ~OPOST;
|
|
ret = tcsetattr(master_pty, TCSANOW, &ter);
|
|
if (ret == -1) {
|
|
ERROR("Could not change terminal driver settings\n");
|
|
}
|
|
|
|
nsi_print_trace("%s connected to pseudotty: %s\n",
|
|
uart_name, slave_pty_name);
|
|
|
|
if (wait_pts) {
|
|
/*
|
|
* This trick sets the HUP flag on the pty master, making it
|
|
* possible to detect a client connection using poll.
|
|
* The connection of the client would cause the HUP flag to be
|
|
* cleared, and in turn set again at disconnect.
|
|
*/
|
|
ret = open(slave_pty_name, O_RDWR | O_NOCTTY);
|
|
if (ret == -1) {
|
|
err_nbr = errno;
|
|
ERROR("%s: Could not open terminal from the slave side (%i,%s)\n",
|
|
__func__, err_nbr, strerror(err_nbr));
|
|
}
|
|
ret = close(ret);
|
|
if (ret == -1) {
|
|
err_nbr = errno;
|
|
ERROR("%s: Could not close terminal from the slave side (%i,%s)\n",
|
|
__func__, err_nbr, strerror(err_nbr));
|
|
}
|
|
}
|
|
if (do_auto_attach) {
|
|
attach_to_pty(slave_pty_name, auto_attach_cmd);
|
|
}
|
|
|
|
return master_pty;
|
|
}
|
|
|
|
int np_uart_pty_get_stdin_fileno(void)
|
|
{
|
|
return STDIN_FILENO;
|
|
}
|
|
|
|
int np_uart_pty_get_stdout_fileno(void)
|
|
{
|
|
return STDOUT_FILENO;
|
|
}
|