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