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