1 /*
2  * Copyright (c) 2024 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 /*
8  * UART PTY backend internal functions
9  */
10 
11 #undef _XOPEN_SOURCE
12 /* Note: This is used only for interaction with the host C library, and is therefore exempt of
13  * coding guidelines rule A.4&5 which applies to the embedded code using embedded libraries
14  */
15 #define _XOPEN_SOURCE 600
16 
17 #undef _POSIX_C_SOURCE
18 #define _POSIX_C_SOURCE 200809L
19 
20 #include <stdbool.h>
21 #include <stdint.h>
22 #include <time.h>
23 #include <errno.h>
24 #include <stddef.h>
25 #include <stdlib.h>
26 #include <stdio.h>
27 #include <string.h>
28 #include <pty.h>
29 #include <fcntl.h>
30 #include <sys/select.h>
31 #include <unistd.h>
32 #include <poll.h>
33 #include <nsi_tracing.h>
34 
35 #define ERROR nsi_print_error_and_exit
36 #define WARN nsi_print_warning
37 
38 /**
39  * @brief Check if the output descriptor has something connected to the slave side
40  *
41  * @param fd file number
42  *
43  * @retval 0 Nothing connected yet
44  * @retval 1 Something connected to the slave side
45  */
nhw_upty_slave_connected(int fd)46 static int nhw_upty_slave_connected(int fd)
47 {
48   struct pollfd pfd = { .fd = fd, .events = POLLHUP };
49   int ret;
50 
51   ret = poll(&pfd, 1, 0);
52   if (ret == -1) {
53     int err = errno;
54     /*
55      * Possible errors are:
56      *  * EINTR :A signal was received => ok
57      *  * EFAULT and EINVAL: parameters/programming error
58      *  * ENOMEM no RAM left
59      */
60     if (err != EINTR) {
61       ERROR("%s: unexpected error during poll, errno=%i,%s\n",
62             __func__, err, strerror(err));
63     }
64   }
65   if (!(pfd.revents & POLLHUP)) {
66     /* There is now a reader on the slave side */
67     return 1;
68   }
69   return 0;
70 }
71 
72 /**
73  * Attempt to connect a terminal emulator to the slave side of the pty
74  * If -attach_uart_cmd=<cmd> is provided as a command line option, <cmd> will be
75  * used. Otherwise, the default command,
76  * CONFIG_NATIVE_UART_AUTOATTACH_DEFAULT_CMD, will be used instead
77  */
attach_to_tty(const char * slave_tty,const char * auto_attach_cmd)78 static void attach_to_tty(const char *slave_tty, const char *auto_attach_cmd)
79 {
80   char command[strlen(auto_attach_cmd) + strlen(slave_tty) + 1];
81 
82   sprintf(command, auto_attach_cmd, slave_tty);
83 
84   int ret = system(command);
85 
86   if (ret != 0) {
87     WARN("Could not attach to the UART with \"%s\"\n", command);
88     WARN("The command returned %i\n", WEXITSTATUS(ret));
89   }
90 }
91 /**
92  * Attempt to allocate and open a new pseudoterminal
93  *
94  * Returns the file descriptor of the master side
95  * If auto_attach was set, it will also attempt to connect a new terminal
96  * emulator to its slave side.
97  */
nhw_upty_open_ptty(const char * uart_name,const char * auto_attach_cmd,bool do_auto_attach,bool wait_pts)98 int nhw_upty_open_ptty(const char *uart_name, const char *auto_attach_cmd,
99           bool do_auto_attach, bool wait_pts)
100 {
101   int master_pty;
102   char *slave_pty_name;
103   struct termios ter;
104   int err_nbr;
105   int ret;
106   int flags;
107 
108   master_pty = posix_openpt(O_RDWR | O_NOCTTY);
109   if (master_pty == -1) {
110     ERROR("Could not open a new TTY for the UART\n");
111   }
112   ret = grantpt(master_pty);
113   if (ret == -1) {
114     err_nbr = errno;
115     close(master_pty);
116     ERROR("Could not grant access to the slave PTY side (%i)\n",
117       err_nbr);
118   }
119   ret = unlockpt(master_pty);
120   if (ret == -1) {
121     err_nbr = errno;
122     close(master_pty);
123     ERROR("Could not unlock the slave PTY side (%i)\n", err_nbr);
124   }
125   slave_pty_name = ptsname(master_pty);
126   if (slave_pty_name == NULL) {
127     err_nbr = errno;
128     close(master_pty);
129     ERROR("Error getting slave PTY device name (%i)\n", err_nbr);
130   }
131   /* Set the master PTY as non blocking */
132   flags = fcntl(master_pty, F_GETFL);
133   if (flags == -1) {
134     err_nbr = errno;
135     close(master_pty);
136     ERROR("Could not read the master PTY file status flags (%i)\n",
137       err_nbr);
138   }
139 
140   ret = fcntl(master_pty, F_SETFL, flags | O_NONBLOCK);
141   if (ret == -1) {
142     err_nbr = errno;
143     close(master_pty);
144     ERROR("Could not set the master PTY as non-blocking (%i)\n",
145       err_nbr);
146   }
147 
148   (void) err_nbr;
149 
150   /*
151    * Set terminal in "raw" mode:
152    *  Not canonical (no line input)
153    *  No signal generation from Ctr+{C|Z..}
154    *  No echoing, no input or output processing
155    *  No replacing of NL or CR
156    *  No flow control
157    */
158   ret = tcgetattr(master_pty, &ter);
159   if (ret == -1) {
160     ERROR("Could not read terminal driver settings\n");
161   }
162   ter.c_cc[VMIN] = 0;
163   ter.c_cc[VTIME] = 0;
164   ter.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO);
165   ter.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR | INPCK
166        | ISTRIP | IXON | PARMRK);
167   ter.c_oflag &= ~OPOST;
168   ret = tcsetattr(master_pty, TCSANOW, &ter);
169   if (ret == -1) {
170     ERROR("Could not change terminal driver settings\n");
171   }
172 
173   nsi_print_trace("%s connected to pseudotty: %s\n",
174         uart_name, slave_pty_name);
175 
176   if (wait_pts) {
177     /*
178      * This trick sets the HUP flag on the tty master, making it
179      * possible to detect a client connection using poll.
180      * The connection of the client would cause the HUP flag to be
181      * cleared, and in turn set again at disconnect.
182      */
183     ret = open(slave_pty_name, O_RDWR | O_NOCTTY);
184     if (ret == -1) {
185       err_nbr = errno;
186       ERROR("%s: Could not open terminal from the slave side (%i,%s)\n",
187         __func__, err_nbr, strerror(err_nbr));
188     }
189     ret = close(ret);
190     if (ret == -1) {
191       err_nbr = errno;
192       ERROR("%s: Could not close terminal from the slave side (%i,%s)\n",
193         __func__, err_nbr, strerror(err_nbr));
194     }
195   }
196   if (do_auto_attach) {
197     attach_to_tty(slave_pty_name, auto_attach_cmd);
198   }
199 
200   return master_pty;
201 }
202 
nhw_upty_wait_for_pty(int fd,uint64_t microsec)203 void nhw_upty_wait_for_pty(int fd, uint64_t microsec) {
204   while (1) {
205     int ret = nhw_upty_slave_connected(fd);
206 
207     if (ret == 1) {
208       break;
209     }
210 
211     struct timespec tv;
212 
213     tv.tv_sec = microsec / 1e6;
214     tv.tv_nsec = (microsec - tv.tv_sec*1e6)*1e3;
215     (void)nanosleep(&tv, NULL);
216   }
217 }
218