| /* |
| * 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; |
| } |