1 /*
2  * Copyright (c) 2018, Oticon A/S
3  * Copyright (c) 2025, Nordic Semiconductor ASA
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #include <zephyr/devicetree.h>
9 #if DT_HAS_COMPAT_STATUS_OKAY(zephyr_native_posix_uart)
10 #define DT_DRV_COMPAT zephyr_native_posix_uart
11 #warning "zephyr,native-posix-uart is deprecated in favor of zephyr,native-pty-uart"
12 #else
13 #define DT_DRV_COMPAT zephyr_native_pty_uart
14 #endif
15 
16 #include <stdbool.h>
17 #include <zephyr/drivers/uart.h>
18 #include <zephyr/kernel.h>
19 #include <cmdline.h> /* native_sim command line options header */
20 #include <posix_native_task.h>
21 #include <nsi_host_trampolines.h>
22 #include <nsi_tracing.h>
23 #include "uart_native_pty_bottom.h"
24 
25 #define ERROR posix_print_error_and_exit
26 #define WARN posix_print_warning
27 
28 /*
29  * UART driver for native simulator based boards.
30  * It can support a configurable number of UARTs.
31  *
32  * One (and only one) of this can be connected to the process STDIN+STDOUT otherwise, they are
33  * connected to a dedicated pseudo terminal.
34  *
35  * Connecting to a dedicated PTY is the recommended option for interactive use, as the pseudo
36  * terminal driver will be configured in "raw" mode and will therefore behave more like a real UART.
37  *
38  * When connected to its own pseudo terminal, it may also auto attach a terminal emulator to it,
39  * if set so from command line.
40  */
41 
42 struct native_pty_status {
43 	int out_fd;       /* File descriptor used for output */
44 	int in_fd;        /* File descriptor used for input */
45 	bool on_stdinout; /* This UART is connected to a PTY and not STDIN/OUT */
46 
47 	bool auto_attach;      /* For PTY, attach a terminal emulator automatically */
48 	char *auto_attach_cmd; /* If auto_attach, which command to launch the terminal emulator */
49 	bool wait_pts;         /* Hold writes to the uart/pts until a client is connected/ready */
50 	bool cmd_request_stdinout; /* User requested to connect this UART to the stdin/out */
51 };
52 
53 static void np_uart_poll_out(const struct device *dev, unsigned char out_char);
54 static int np_uart_poll_in(const struct device *dev, unsigned char *p_char);
55 static int np_uart_init(const struct device *dev);
56 
57 static DEVICE_API(uart, np_uart_driver_api) = {
58 	.poll_out = np_uart_poll_out,
59 	.poll_in = np_uart_poll_in,
60 };
61 
62 #define NATIVE_PTY_INSTANCE(inst)                                        \
63 	static struct native_pty_status native_pty_status_##inst;        \
64 								         \
65 	DEVICE_DT_INST_DEFINE(inst, np_uart_init, NULL,                  \
66 			      (void *)&native_pty_status_##inst, NULL,   \
67 			      PRE_KERNEL_1, CONFIG_SERIAL_INIT_PRIORITY, \
68 			      &np_uart_driver_api);
69 
70 DT_INST_FOREACH_STATUS_OKAY(NATIVE_PTY_INSTANCE);
71 
72 /**
73  * @brief Initialize a native_pty serial port
74  *
75  * @param dev uart device struct
76  *
77  * @return 0 (if it fails catastrophically, the execution is terminated)
78  */
np_uart_init(const struct device * dev)79 static int np_uart_init(const struct device *dev)
80 {
81 
82 	static bool stdinout_used;
83 	struct native_pty_status *d;
84 
85 	d = (struct native_pty_status *)dev->data;
86 
87 	if (IS_ENABLED(CONFIG_UART_NATIVE_PTY_0_ON_STDINOUT)) {
88 		static bool first_node = true;
89 
90 		if (first_node) {
91 			d->on_stdinout = true;
92 		}
93 		first_node = false;
94 	}
95 
96 	if (d->cmd_request_stdinout) {
97 		if (stdinout_used) {
98 			nsi_print_warning("%s requested to connect to STDIN/OUT, but another UART"
99 					  " is already connected to it => ignoring request.\n",
100 					  dev->name);
101 		} else {
102 			d->on_stdinout = true;
103 		}
104 	}
105 
106 	if (d->on_stdinout == true) {
107 		d->in_fd  = np_uart_pty_get_stdin_fileno();
108 		d->out_fd = np_uart_pty_get_stdout_fileno();
109 		stdinout_used = true;
110 	} else {
111 		if (d->auto_attach_cmd == NULL) {
112 			d->auto_attach_cmd = CONFIG_UART_NATIVE_PTY_AUTOATTACH_DEFAULT_CMD;
113 		} else { /* Running with --attach_uart_cmd, implies --attach_uart */
114 			d->auto_attach = true;
115 		}
116 		int tty_fn = np_uart_open_pty(dev->name, d->auto_attach_cmd, d->auto_attach,
117 					      d->wait_pts);
118 		d->in_fd = tty_fn;
119 		d->out_fd = tty_fn;
120 	}
121 
122 	return 0;
123 }
124 
125 /*
126  * @brief Output a character towards the serial port
127  *
128  * @param dev UART device struct
129  * @param out_char Character to send.
130  */
np_uart_poll_out(const struct device * dev,unsigned char out_char)131 static void np_uart_poll_out(const struct device *dev, unsigned char out_char)
132 {
133 	int ret;
134 	struct native_pty_status *d = (struct native_pty_status *)dev->data;
135 
136 	if (d->wait_pts) {
137 		while (1) {
138 			int rc = np_uart_slave_connected(d->out_fd);
139 
140 			if (rc == 1) {
141 				break;
142 			}
143 			k_sleep(K_MSEC(100));
144 		}
145 	}
146 
147 	/* The return value of write() cannot be ignored (there is a warning)
148 	 * but we do not need the return value for anything.
149 	 */
150 	ret = nsi_host_write(d->out_fd, &out_char, 1);
151 	(void) ret;
152 }
153 
154 /**
155  * @brief Poll the device for input.
156  *
157  * @param dev UART device structure.
158  * @param p_char Pointer to character.
159  *
160  * @retval 0 If a character arrived and was stored in p_char
161  * @retval -1 If no character was available to read
162  */
np_uart_stdin_poll_in(const struct device * dev,unsigned char * p_char)163 static int np_uart_stdin_poll_in(const struct device *dev, unsigned char *p_char)
164 {
165 	int in_f = ((struct native_pty_status *)dev->data)->in_fd;
166 	static bool disconnected;
167 	int rc;
168 
169 	if (disconnected == true) {
170 		return -1;
171 	}
172 
173 	rc = np_uart_stdin_poll_in_bottom(in_f, p_char);
174 	if (rc == -2) {
175 		disconnected = true;
176 		return -1;
177 	}
178 
179 	return rc;
180 }
181 
182 /**
183  * @brief Poll the device for input.
184  *
185  * @param dev UART device structure.
186  * @param p_char Pointer to character.
187  *
188  * @retval 0 If a character arrived and was stored in p_char
189  * @retval -1 If no character was available to read
190  */
np_uart_pty_poll_in(const struct device * dev,unsigned char * p_char)191 static int np_uart_pty_poll_in(const struct device *dev, unsigned char *p_char)
192 {
193 	int n = -1;
194 	int in_f = ((struct native_pty_status *)dev->data)->in_fd;
195 
196 	n = nsi_host_read(in_f, p_char, 1);
197 	if (n == -1) {
198 		return -1;
199 	}
200 	return 0;
201 }
202 
np_uart_poll_in(const struct device * dev,unsigned char * p_char)203 static int np_uart_poll_in(const struct device *dev, unsigned char *p_char)
204 {
205 	if (((struct native_pty_status *)dev->data)->on_stdinout) {
206 		return np_uart_stdin_poll_in(dev, p_char);
207 	} else {
208 		return np_uart_pty_poll_in(dev, p_char);
209 	}
210 }
211 
212 #define NATIVE_PTY_SET_AUTO_ATTACH_CMD(inst, cmd)      \
213 	native_pty_status_##inst.auto_attach_cmd = cmd;
214 #define NATIVE_PTY_SET_AUTO_ATTACH(inst, value)        \
215 	native_pty_status_##inst.auto_attach = value;
216 #define NATIVE_PTY_SET_WAIT_PTS(inst, value)           \
217 	native_pty_status_##inst.wait_pts = value;
218 
auto_attach_cmd_cb(char * argv,int offset)219 static void auto_attach_cmd_cb(char *argv, int offset)
220 {
221 	DT_INST_FOREACH_STATUS_OKAY_VARGS(NATIVE_PTY_SET_AUTO_ATTACH_CMD, &argv[offset]);
222 	DT_INST_FOREACH_STATUS_OKAY_VARGS(NATIVE_PTY_SET_AUTO_ATTACH, true);
223 }
224 
auto_attach_cb(char * argv,int offset)225 static void auto_attach_cb(char *argv, int offset)
226 {
227 	DT_INST_FOREACH_STATUS_OKAY_VARGS(NATIVE_PTY_SET_AUTO_ATTACH, true);
228 }
229 
wait_pts_cb(char * argv,int offset)230 static void wait_pts_cb(char *argv, int offset)
231 {
232 	DT_INST_FOREACH_STATUS_OKAY_VARGS(NATIVE_PTY_SET_WAIT_PTS, true);
233 }
234 
235 #define INST_NAME(inst) DEVICE_DT_NAME(DT_DRV_INST(inst))
236 
237 #define NATIVE_PTY_COMMAND_LINE_OPTS(inst)                                                         \
238 	{                                                                                          \
239 		.is_switch = true,                                                                 \
240 		.option = INST_NAME(inst) "_stdinout",                                             \
241 		.type = 'b',                                                                       \
242 		.dest = &native_pty_status_##inst.cmd_request_stdinout,                            \
243 		.descript = "Connect "INST_NAME(inst)" to STDIN/OUT instead of a PTY"              \
244 			    " (can only be done for one UART)"                                     \
245 	},                                                                                         \
246 	{                                                                                          \
247 		.is_switch = true,                                                                 \
248 		.option = INST_NAME(inst) "_attach_uart",                                          \
249 		.type = 'b',                                                                       \
250 		.dest = &native_pty_status_##inst.auto_attach,                                     \
251 		.descript = "Automatically attach "INST_NAME(inst)" to a terminal emulator."       \
252 			    " (only applicable when connected to PTYs)"                            \
253 	},                                                                                         \
254 	{                                                                                          \
255 		.option = INST_NAME(inst) "_attach_uart_cmd",                                      \
256 		.name = "\"cmd\"",                                                                 \
257 		.type = 's',                                                                       \
258 		.dest = &native_pty_status_##inst.auto_attach_cmd,                                 \
259 		.descript = "Command used to automatically attach to the terminal "INST_NAME(inst) \
260 			    " (implies "INST_NAME(inst)"_auto_attach), by default: "               \
261 			    "'" CONFIG_UART_NATIVE_PTY_AUTOATTACH_DEFAULT_CMD "'"                  \
262 			    " (only applicable when connected to PTYs)"                            \
263 	},                                                                                         \
264 	{                                                                                          \
265 		.is_switch = true,                                                                 \
266 		.option = INST_NAME(inst) "_wait_uart",                                            \
267 		.type = 'b',                                                                       \
268 		.dest = &native_pty_status_##inst.wait_pts,                                        \
269 		.descript = "Hold writes to "INST_NAME(inst)" until a client is connected/ready"   \
270 			   " (only applicable when connected to PTYs)"                             \
271 	},
272 
np_add_uart_options(void)273 static void np_add_uart_options(void)
274 {
275 	static struct args_struct_t uart_options[] = {
276 	/* Set of parameters that apply to all PTY UARTs: */
277 	{
278 		.is_switch = true,
279 		.option = "attach_uart",
280 		.type = 'b',
281 		.call_when_found = auto_attach_cb,
282 		.descript = "Automatically attach all PTY UARTs to a terminal emulator."
283 			    " (only applicable when connected to PTYs)"
284 	},
285 	{
286 		.option = "attach_uart_cmd",
287 		.name = "\"cmd\"",
288 		.type = 's',
289 		.call_when_found = auto_attach_cmd_cb,
290 		.descript = "Command used to automatically attach all PTY UARTs to a terminal "
291 			    "emulator (implies auto_attach), by default: "
292 			    "'" CONFIG_UART_NATIVE_PTY_AUTOATTACH_DEFAULT_CMD "'"
293 			    " (only applicable when connected to PTYs)"
294 	},
295 	{
296 		.is_switch = true,
297 		.option = "wait_uart",
298 		.type = 'b',
299 		.call_when_found = wait_pts_cb,
300 		.descript = "Hold writes to all PTY UARTs until a client is connected/ready"
301 			    " (only applicable when connected to PTYs)"
302 	},
303 	/* Set of parameters that apply to each individual PTY UART: */
304 	DT_INST_FOREACH_STATUS_OKAY(NATIVE_PTY_COMMAND_LINE_OPTS)
305 	ARG_TABLE_ENDMARKER
306 	};
307 
308 	native_add_command_line_opts(uart_options);
309 }
310 
311 #define NATIVE_PTY_CLEANUP(inst)                                                                \
312 	if ((!native_pty_status_##inst.on_stdinout) && (native_pty_status_##inst.in_fd != 0)) { \
313 		nsi_host_close(native_pty_status_##inst.in_fd);                                 \
314 		native_pty_status_##inst.in_fd = 0;                                             \
315 	}
316 
np_cleanup_uart(void)317 static void np_cleanup_uart(void)
318 {
319 	DT_INST_FOREACH_STATUS_OKAY(NATIVE_PTY_CLEANUP);
320 }
321 
322 NATIVE_TASK(np_add_uart_options, PRE_BOOT_1, 11);
323 NATIVE_TASK(np_cleanup_uart, ON_EXIT, 99);
324