1 /*
2  * Copyright (c) 2018, Oticon A/S
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT zephyr_native_posix_uart
8 
9 #include <errno.h>
10 #include <stddef.h>
11 #include <stdlib.h>
12 #include <stdio.h>
13 #include <string.h>
14 #include <pty.h>
15 #include <fcntl.h>
16 #include <sys/select.h>
17 #include <unistd.h>
18 #include <poll.h>
19 
20 #include <drivers/uart.h>
21 #include "cmdline.h" /* native_posix command line options header */
22 #include "soc.h"
23 
24 /*
25  * UART driver for POSIX ARCH based boards.
26  * It can support up to two UARTs.
27  *
28  * For the first UART:
29  *
30  * It can either be connected to the process STDIN+STDOUT
31  * OR
32  * to a dedicated pseudo terminal
33  *
34  * The 2nd option is the recommended one for interactive use, as the pseudo
35  * terminal driver will be configured in "raw" mode, and will therefore behave
36  * more like a real UART.
37  *
38  * When connected to its own pseudo terminal, it may also auto attach a terminal
39  * emulator to it, if set so from command line.
40  */
41 
42 static int np_uart_stdin_poll_in(const struct device *dev,
43 				 unsigned char *p_char);
44 static int np_uart_tty_poll_in(const struct device *dev,
45 			       unsigned char *p_char);
46 static void np_uart_poll_out(const struct device *dev,
47 				      unsigned char out_char);
48 
49 static bool auto_attach;
50 static bool wait_pts;
51 static const char default_cmd[] = CONFIG_NATIVE_UART_AUTOATTACH_DEFAULT_CMD;
52 static char *auto_attach_cmd;
53 
54 struct native_uart_status {
55 	int out_fd; /* File descriptor used for output */
56 	int in_fd; /* File descriptor used for input */
57 };
58 
59 static struct native_uart_status native_uart_status_0;
60 
61 static struct uart_driver_api np_uart_driver_api_0 = {
62 	.poll_out = np_uart_poll_out,
63 	.poll_in = np_uart_tty_poll_in,
64 };
65 
66 #if defined(CONFIG_UART_NATIVE_POSIX_PORT_1_ENABLE)
67 static struct native_uart_status native_uart_status_1;
68 
69 static struct uart_driver_api np_uart_driver_api_1 = {
70 	.poll_out = np_uart_poll_out,
71 	.poll_in = np_uart_tty_poll_in,
72 };
73 #endif /* CONFIG_UART_NATIVE_POSIX_PORT_1_ENABLE */
74 
75 #define ERROR posix_print_error_and_exit
76 #define WARN posix_print_warning
77 
78 /**
79  * Attempt to connect a terminal emulator to the slave side of the pty
80  * If -attach_uart_cmd=<cmd> is provided as a command line option, <cmd> will be
81  * used. Otherwise, the default command,
82  * CONFIG_NATIVE_UART_AUTOATTACH_DEFAULT_CMD, will be used instead
83  */
attach_to_tty(const char * slave_tty)84 static void attach_to_tty(const char *slave_tty)
85 {
86 	if (auto_attach_cmd == NULL) {
87 		auto_attach_cmd = (char *)default_cmd;
88 	}
89 	char command[strlen(auto_attach_cmd) + strlen(slave_tty) + 1];
90 
91 	sprintf(command, auto_attach_cmd, slave_tty);
92 
93 	int ret = system(command);
94 
95 	if (ret != 0) {
96 		WARN("Could not attach to the UART with \"%s\"\n", command);
97 		WARN("The command returned %i\n", WEXITSTATUS(ret));
98 	}
99 }
100 
101 /**
102  * Attempt to allocate and open a new pseudoterminal
103  *
104  * Returns the file descriptor of the master side
105  * If auto_attach was set, it will also attempt to connect a new terminal
106  * emulator to its slave side.
107  */
open_tty(struct native_uart_status * driver_data,const char * uart_name,bool do_auto_attach)108 static int open_tty(struct native_uart_status *driver_data,
109 		    const char *uart_name,
110 		    bool do_auto_attach)
111 {
112 	int master_pty;
113 	char *slave_pty_name;
114 	struct termios ter;
115 	struct winsize win;
116 	int err_nbr;
117 	int ret;
118 	int flags;
119 
120 	win.ws_col = 80;
121 	win.ws_row = 24;
122 
123 	master_pty = posix_openpt(O_RDWR | O_NOCTTY);
124 	if (master_pty == -1) {
125 		ERROR("Could not open a new TTY for the UART\n");
126 	}
127 	ret = grantpt(master_pty);
128 	if (ret == -1) {
129 		err_nbr = errno;
130 		close(master_pty);
131 		ERROR("Could not grant access to the slave PTY side (%i)\n",
132 			errno);
133 	}
134 	ret = unlockpt(master_pty);
135 	if (ret == -1) {
136 		err_nbr = errno;
137 		close(master_pty);
138 		ERROR("Could not unlock the slave PTY side (%i)\n", errno);
139 	}
140 	slave_pty_name = ptsname(master_pty);
141 	if (slave_pty_name == NULL) {
142 		err_nbr = errno;
143 		close(master_pty);
144 		ERROR("Error getting slave PTY device name (%i)\n", errno);
145 	}
146 	/* Set the master PTY as non blocking */
147 	flags = fcntl(master_pty, F_GETFL);
148 	if (flags == -1) {
149 		err_nbr = errno;
150 		close(master_pty);
151 		ERROR("Could not read the master PTY file status flags (%i)\n",
152 			errno);
153 	}
154 
155 	ret = fcntl(master_pty, F_SETFL, flags | O_NONBLOCK);
156 	if (ret == -1) {
157 		err_nbr = errno;
158 		close(master_pty);
159 		ERROR("Could not set the master PTY as non-blocking (%i)\n",
160 			errno);
161 	}
162 
163 	/*
164 	 * Set terminal in "raw" mode:
165 	 *  Not canonical (no line input)
166 	 *  No signal generation from Ctr+{C|Z..}
167 	 *  No echoing, no input or output processing
168 	 *  No replacing of NL or CR
169 	 *  No flow control
170 	 */
171 	ret = tcgetattr(master_pty, &ter);
172 	if (ret == -1) {
173 		ERROR("Could not read terminal driver settings\n");
174 	}
175 	ter.c_cc[VMIN] = 0;
176 	ter.c_cc[VTIME] = 0;
177 	ter.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO);
178 	ter.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR | INPCK
179 			 | ISTRIP | IXON | PARMRK);
180 	ter.c_oflag &= ~OPOST;
181 	ret = tcsetattr(master_pty, TCSANOW, &ter);
182 	if (ret == -1) {
183 		ERROR("Could not change terminal driver settings\n");
184 	}
185 
186 	posix_print_trace("%s connected to pseudotty: %s\n",
187 			  uart_name, slave_pty_name);
188 
189 	if (wait_pts) {
190 		/*
191 		 * This trick sets the HUP flag on the tty master, making it
192 		 * possible to detect a client connection using poll.
193 		 * The connection of the client would cause the HUP flag to be
194 		 * cleared, and in turn set again at disconnect.
195 		 */
196 		close(open(slave_pty_name, O_RDWR | O_NOCTTY));
197 	}
198 	if (do_auto_attach) {
199 		attach_to_tty(slave_pty_name);
200 	}
201 
202 	return master_pty;
203 }
204 
205 /**
206  * @brief Initialize the first native_posix serial port
207  *
208  * @param dev UART_0 device struct
209  *
210  * @return 0 (if it fails catastrophically, the execution is terminated)
211  */
np_uart_0_init(const struct device * dev)212 static int np_uart_0_init(const struct device *dev)
213 {
214 	struct native_uart_status *d;
215 
216 	d = (struct native_uart_status *)dev->data;
217 
218 	if (IS_ENABLED(CONFIG_NATIVE_UART_0_ON_OWN_PTY)) {
219 		int tty_fn = open_tty(d, DT_INST_LABEL(0),
220 				      auto_attach);
221 
222 		d->in_fd = tty_fn;
223 		d->out_fd = tty_fn;
224 		np_uart_driver_api_0.poll_in = np_uart_tty_poll_in;
225 	} else { /* NATIVE_UART_0_ON_STDINOUT */
226 		d->in_fd  = STDIN_FILENO;
227 		d->out_fd = STDOUT_FILENO;
228 		np_uart_driver_api_0.poll_in = np_uart_stdin_poll_in;
229 
230 		if (isatty(STDIN_FILENO)) {
231 			WARN("The UART driver has been configured to map to the"
232 			     " process stdin&out (NATIVE_UART_0_ON_STDINOUT), "
233 			     "but stdin seems to be left attached to the shell."
234 			     " This will most likely NOT behave as you want it "
235 			     "to. This option is NOT meant for interactive use "
236 			     "but for piping/feeding from/to files to the UART"
237 			     );
238 		}
239 	}
240 
241 	return 0;
242 }
243 
244 #if defined(CONFIG_UART_NATIVE_POSIX_PORT_1_ENABLE)
245 /*
246  * Initialize the 2nd UART port.
247  * This port will be always attached to its own new pseudoterminal.
248  */
np_uart_1_init(const struct device * dev)249 static int np_uart_1_init(const struct device *dev)
250 {
251 	struct native_uart_status *d;
252 	int tty_fn;
253 
254 	d = (struct native_uart_status *)dev->data;
255 
256 	tty_fn = open_tty(d, DT_INST_LABEL(1), false);
257 
258 	d->in_fd = tty_fn;
259 	d->out_fd = tty_fn;
260 
261 	return 0;
262 }
263 #endif
264 
265 /*
266  * @brief Output a character towards the serial port
267  *
268  * @param dev UART device struct
269  * @param out_char Character to send.
270  */
np_uart_poll_out(const struct device * dev,unsigned char out_char)271 static void np_uart_poll_out(const struct device *dev,
272 				      unsigned char out_char)
273 {
274 	int ret;
275 	struct native_uart_status *d = (struct native_uart_status *)dev->data;
276 
277 	if (wait_pts) {
278 		struct pollfd pfd = { .fd = d->out_fd, .events = POLLHUP };
279 
280 		while (1) {
281 			poll(&pfd, 1, 0);
282 			if (!(pfd.revents & POLLHUP)) {
283 				/* There is now a reader on the slave side */
284 				break;
285 			}
286 			k_sleep(K_MSEC(100));
287 		}
288 	}
289 
290 	/* The return value of write() cannot be ignored (there is a warning)
291 	 * but we do not need the return value for anything.
292 	 */
293 	ret = write(d->out_fd, &out_char, 1);
294 }
295 
296 /**
297  * @brief Poll the device for input.
298  *
299  * @param dev UART device structure.
300  * @param p_char Pointer to character.
301  *
302  * @retval 0 If a character arrived and was stored in p_char
303  * @retval -1 If no character was available to read
304  */
np_uart_stdin_poll_in(const struct device * dev,unsigned char * p_char)305 static int np_uart_stdin_poll_in(const struct device *dev,
306 				 unsigned char *p_char)
307 {
308 	static bool disconnected;
309 
310 	if (disconnected || feof(stdin)) {
311 		/*
312 		 * The stdinput is fed from a file which finished or the user
313 		 * pressed Crtl+D
314 		 */
315 		disconnected = true;
316 		return -1;
317 	}
318 
319 	int n = -1;
320 	int in_f = ((struct native_uart_status *)dev->data)->in_fd;
321 
322 	int ready;
323 	fd_set readfds;
324 	static struct timeval timeout; /* just zero */
325 
326 	FD_ZERO(&readfds);
327 	FD_SET(in_f, &readfds);
328 
329 	ready = select(in_f+1, &readfds, NULL, NULL, &timeout);
330 
331 	if (ready == 0) {
332 		return -1;
333 	} else if (ready == -1) {
334 		ERROR("%s: Error on select ()\n", __func__);
335 	}
336 
337 	n = read(in_f, p_char, 1);
338 	if (n == -1) {
339 		return -1;
340 	}
341 
342 	return 0;
343 }
344 
345 /**
346  * @brief Poll the device for input.
347  *
348  * @param dev UART device structure.
349  * @param p_char Pointer to character.
350  *
351  * @retval 0 If a character arrived and was stored in p_char
352  * @retval -1 If no character was available to read
353  */
np_uart_tty_poll_in(const struct device * dev,unsigned char * p_char)354 static int np_uart_tty_poll_in(const struct device *dev,
355 			       unsigned char *p_char)
356 {
357 	int n = -1;
358 	int in_f = ((struct native_uart_status *)dev->data)->in_fd;
359 
360 	n = read(in_f, p_char, 1);
361 	if (n == -1) {
362 		return -1;
363 	}
364 	return 0;
365 }
366 
367 DEVICE_DT_INST_DEFINE(0,
368 	    &np_uart_0_init, NULL,
369 	    (void *)&native_uart_status_0, NULL,
370 	    PRE_KERNEL_1, CONFIG_SERIAL_INIT_PRIORITY,
371 	    &np_uart_driver_api_0);
372 
373 #if defined(CONFIG_UART_NATIVE_POSIX_PORT_1_ENABLE)
374 DEVICE_DT_INST_DEFINE(1,
375 	    &np_uart_1_init, NULL,
376 	    (void *)&native_uart_status_1, NULL,
377 	    PRE_KERNEL_1, CONFIG_SERIAL_INIT_PRIORITY,
378 	    &np_uart_driver_api_1);
379 #endif /* CONFIG_UART_NATIVE_POSIX_PORT_1_ENABLE */
380 
auto_attach_cmd_cb(char * argv,int offset)381 static void auto_attach_cmd_cb(char *argv, int offset)
382 {
383 	ARG_UNUSED(argv);
384 	ARG_UNUSED(offset);
385 
386 	auto_attach = true;
387 }
388 
np_add_uart_options(void)389 static void np_add_uart_options(void)
390 {
391 	if (!IS_ENABLED(CONFIG_NATIVE_UART_0_ON_OWN_PTY)) {
392 		return;
393 	}
394 
395 	static struct args_struct_t uart_options[] = {
396 		/*
397 		 * Fields:
398 		 * manual, mandatory, switch,
399 		 * option_name, var_name ,type,
400 		 * destination, callback,
401 		 * description
402 		 */
403 		{false, false, true,
404 		"attach_uart", "", 'b',
405 		(void *)&auto_attach, NULL,
406 		"Automatically attach to the UART terminal"},
407 		{false, false, false,
408 		"attach_uart_cmd", "\"cmd\"", 's',
409 		(void *)&auto_attach_cmd, auto_attach_cmd_cb,
410 		"Command used to automatically attach to the terminal"
411 		"(implies auto_attach), by "
412 		"default: '" CONFIG_NATIVE_UART_AUTOATTACH_DEFAULT_CMD "'"},
413 		IF_ENABLED(CONFIG_UART_NATIVE_WAIT_PTS_READY_ENABLE, (
414 			{false, false, true,
415 			"wait_uart", "", 'b',
416 			(void *)&wait_pts, NULL,
417 			"Hold writes to the uart/pts until a client is "
418 			"connected/ready"},)
419 		)
420 		ARG_TABLE_ENDMARKER
421 	};
422 
423 	native_add_command_line_opts(uart_options);
424 }
425 
np_cleanup_uart(void)426 static void np_cleanup_uart(void)
427 {
428 	if (IS_ENABLED(CONFIG_NATIVE_UART_0_ON_OWN_PTY)) {
429 		if (native_uart_status_0.in_fd != 0) {
430 			close(native_uart_status_0.in_fd);
431 		}
432 	}
433 
434 #if defined(CONFIG_UART_NATIVE_POSIX_PORT_1_ENABLE)
435 	if (native_uart_status_1.in_fd != 0) {
436 		close(native_uart_status_1.in_fd);
437 	}
438 #endif
439 }
440 
441 NATIVE_TASK(np_add_uart_options, PRE_BOOT_1, 11);
442 NATIVE_TASK(np_cleanup_uart, ON_EXIT, 99);
443