blob: 7c44ccf68cc827f0b40aafdffc30c82a074b2f7d [file] [log] [blame]
/*
* 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_tty(const char *slave_tty, const char *auto_attach_cmd)
{
char command[strlen(auto_attach_cmd) + strlen(slave_tty) + 1];
sprintf(command, auto_attach_cmd, slave_tty);
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_ptty(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 TTY 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 tty 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_tty(slave_pty_name, auto_attach_cmd);
}
return master_pty;
}
int np_uart_ptty_get_stdin_fileno(void)
{
return STDIN_FILENO;
}
int np_uart_ptty_get_stdout_fileno(void)
{
return STDOUT_FILENO;
}