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