1 /**
2  * @brief UART Driver for interacting with host serial ports
3  *
4  * @note  Driver can open and send characters to the host serial ports (such as /dev/ttyUSB0 or
5  * /dev/ttyACM0). Only polling Uart API is implemented. Driver can be configured via devicetree,
6  * command line options or at runtime.
7  *
8  * To learn more see Native TTY section at:
9  * https://docs.zephyrproject.org/latest/boards/posix/native_sim/doc/index.html
10  * or
11  * ${ZEPHYR_BASE}/boards/posix/native_sim/doc/index.rst
12  *
13  * Copyright (c) 2023 Marko Sagadin
14  * SPDX-License-Identifier: Apache-2.0
15  */
16 
17 #include <zephyr/device.h>
18 #include <zephyr/drivers/uart.h>
19 #include <zephyr/kernel.h>
20 
21 #include <nsi_tracing.h>
22 
23 #include "cmdline.h"
24 #include "posix_native_task.h"
25 #include "uart_native_tty_bottom.h"
26 #include "nsi_host_trampolines.h"
27 
28 #define WARN(...)  nsi_print_warning(__VA_ARGS__)
29 #define ERROR(...) nsi_print_error_and_exit(__VA_ARGS__)
30 
31 #define DT_DRV_COMPAT zephyr_native_tty_uart
32 
33 struct native_tty_data {
34 	/* File descriptor used for the tty device. */
35 	int fd;
36 	/* Absolute path to the tty device. */
37 	char *serial_port;
38 	/* Baudrate set from the command line. If UINT32_MAX, it was not set. */
39 	int cmd_baudrate;
40 	/* Serial port set from the command line. If NULL, it was not set. */
41 	char *cmd_serial_port;
42 #ifdef CONFIG_UART_INTERRUPT_DRIVEN
43 	/* Emulated tx irq is enabled. */
44 	bool tx_irq_enabled;
45 	/* Emulated rx irq is enabled. */
46 	bool rx_irq_enabled;
47 	/* IRQ callback */
48 	uart_irq_callback_user_data_t callback;
49 	/* IRQ callback data */
50 	void *cb_data;
51 #endif
52 };
53 
54 struct native_tty_config {
55 	struct uart_config uart_config;
56 };
57 
58 #ifdef CONFIG_UART_INTERRUPT_DRIVEN
59 static struct k_thread rx_thread;
60 static K_KERNEL_STACK_DEFINE(rx_stack, CONFIG_ARCH_POSIX_RECOMMENDED_STACK_SIZE);
61 #define NATIVE_TTY_INIT_LEVEL POST_KERNEL
62 #else
63 #define NATIVE_TTY_INIT_LEVEL PRE_KERNEL_1
64 #endif
65 
66 /**
67  * @brief Convert from uart_config to native_tty_bottom_cfg eqvivalent struct
68  *
69  * @param bottom_cfg
70  * @param cfg
71  *
72  * @return 0 on success, negative errno otherwise.
73  */
native_tty_conv_to_bottom_cfg(struct native_tty_bottom_cfg * bottom_cfg,const struct uart_config * cfg)74 static int native_tty_conv_to_bottom_cfg(struct native_tty_bottom_cfg *bottom_cfg,
75 					 const struct uart_config *cfg)
76 {
77 	bottom_cfg->baudrate = cfg->baudrate;
78 
79 	switch (cfg->parity) {
80 	case UART_CFG_PARITY_NONE:
81 		bottom_cfg->parity = NTB_PARITY_NONE;
82 		break;
83 	case UART_CFG_PARITY_ODD:
84 		bottom_cfg->parity = NTB_PARITY_ODD;
85 		break;
86 	case UART_CFG_PARITY_EVEN:
87 		bottom_cfg->parity = NTB_PARITY_EVEN;
88 		break;
89 	default:
90 		return -ENOTSUP;
91 	}
92 
93 	switch (cfg->stop_bits) {
94 	case UART_CFG_STOP_BITS_1:
95 		bottom_cfg->stop_bits = NTB_STOP_BITS_1;
96 		break;
97 	case UART_CFG_STOP_BITS_2:
98 		bottom_cfg->stop_bits = NTB_STOP_BITS_2;
99 		break;
100 	default:
101 		return -ENOTSUP;
102 	}
103 
104 	switch (cfg->data_bits) {
105 	case UART_CFG_DATA_BITS_5:
106 		bottom_cfg->data_bits = NTB_DATA_BITS_5;
107 		break;
108 	case UART_CFG_DATA_BITS_6:
109 		bottom_cfg->data_bits = NTB_DATA_BITS_6;
110 		break;
111 	case UART_CFG_DATA_BITS_7:
112 		bottom_cfg->data_bits = NTB_DATA_BITS_7;
113 		break;
114 	case UART_CFG_DATA_BITS_8:
115 		bottom_cfg->data_bits = NTB_DATA_BITS_8;
116 		break;
117 	default:
118 		return -ENOTSUP;
119 	}
120 
121 	if (cfg->flow_ctrl != UART_CFG_FLOW_CTRL_NONE) {
122 		WARN("Could not set flow control, any kind of hw flow control is not supported.\n");
123 		return -ENOTSUP;
124 	}
125 
126 	bottom_cfg->flow_ctrl = NTB_FLOW_CTRL_NONE;
127 
128 	return 0;
129 }
130 
131 /*
132  * @brief Output a character towards the serial port
133  *
134  * @param dev		UART device structure.
135  * @param out_char	Character to send.
136  */
native_tty_uart_poll_out(const struct device * dev,unsigned char out_char)137 static void native_tty_uart_poll_out(const struct device *dev, unsigned char out_char)
138 {
139 	struct native_tty_data *data = dev->data;
140 
141 	int ret = nsi_host_write(data->fd, &out_char, 1);
142 
143 	if (ret == -1) {
144 		ERROR("Could not write to %s\n", data->serial_port);
145 	}
146 }
147 
148 /**
149  * @brief Poll the device for input.
150  *
151  * @param dev		UART device structure.
152  * @param p_char	Pointer to a character.
153  *
154  * @retval 0	If a character arrived.
155  * @retval -1	If no character was available to read.
156  */
native_tty_uart_poll_in(const struct device * dev,unsigned char * p_char)157 static int native_tty_uart_poll_in(const struct device *dev, unsigned char *p_char)
158 {
159 	struct native_tty_data *data = dev->data;
160 
161 	return nsi_host_read(data->fd, p_char, 1) > 0 ? 0 : -1;
162 }
163 
native_tty_configure(const struct device * dev,const struct uart_config * cfg)164 static int native_tty_configure(const struct device *dev, const struct uart_config *cfg)
165 {
166 	int fd = ((struct native_tty_data *)dev->data)->fd;
167 	struct native_tty_bottom_cfg bottom_cfg;
168 
169 	int rc = native_tty_conv_to_bottom_cfg(&bottom_cfg, cfg);
170 	if (rc) {
171 		WARN("Could not convert uart config to native tty bottom cfg\n");
172 		return rc;
173 	}
174 
175 	return native_tty_configure_bottom(fd, &bottom_cfg);
176 }
177 
178 #ifdef CONFIG_UART_INTERRUPT_DRIVEN
native_tty_uart_fifo_fill(const struct device * dev,const uint8_t * tx_data,int size)179 static int native_tty_uart_fifo_fill(const struct device *dev,
180 				     const uint8_t *tx_data,
181 				     int size)
182 {
183 	struct native_tty_data *data = dev->data;
184 
185 	return nsi_host_write(data->fd, (void *)tx_data, size);
186 }
187 
native_tty_uart_fifo_read(const struct device * dev,uint8_t * rx_data,const int size)188 static int native_tty_uart_fifo_read(const struct device *dev,
189 				     uint8_t *rx_data,
190 				     const int size)
191 {
192 	struct native_tty_data *data = dev->data;
193 
194 	return nsi_host_read(data->fd, rx_data, size);
195 }
196 
native_tty_uart_irq_tx_ready(const struct device * dev)197 static int native_tty_uart_irq_tx_ready(const struct device *dev)
198 {
199 	struct native_tty_data *data = dev->data;
200 
201 	return data->tx_irq_enabled ? 1 : 0;
202 }
203 
native_tty_uart_irq_tx_complete(const struct device * dev)204 static int native_tty_uart_irq_tx_complete(const struct device *dev)
205 {
206 	ARG_UNUSED(dev);
207 	return 1;
208 }
209 
native_tty_uart_irq_tx_enable(const struct device * dev)210 static void native_tty_uart_irq_tx_enable(const struct device *dev)
211 {
212 	struct native_tty_data *data = dev->data;
213 
214 	data->tx_irq_enabled = true;
215 }
216 
native_tty_uart_irq_tx_disable(const struct device * dev)217 static void native_tty_uart_irq_tx_disable(const struct device *dev)
218 {
219 	struct native_tty_data *data = dev->data;
220 
221 	data->tx_irq_enabled = false;
222 }
223 
native_tty_uart_irq_rx_enable(const struct device * dev)224 static void native_tty_uart_irq_rx_enable(const struct device *dev)
225 {
226 	struct native_tty_data *data = dev->data;
227 
228 	data->rx_irq_enabled = true;
229 }
230 
native_tty_uart_irq_rx_disable(const struct device * dev)231 static void native_tty_uart_irq_rx_disable(const struct device *dev)
232 {
233 	struct native_tty_data *data = dev->data;
234 
235 	data->rx_irq_enabled = false;
236 }
237 
native_tty_uart_irq_rx_ready(const struct device * dev)238 static int native_tty_uart_irq_rx_ready(const struct device *dev)
239 {
240 	struct native_tty_data *data = dev->data;
241 
242 	if (data->rx_irq_enabled && native_tty_poll_bottom(data->fd) == 1) {
243 		return 1;
244 	}
245 	return 0;
246 }
247 
native_tty_uart_irq_is_pending(const struct device * dev)248 static int native_tty_uart_irq_is_pending(const struct device *dev)
249 {
250 	return native_tty_uart_irq_rx_ready(dev) ||
251 		native_tty_uart_irq_tx_ready(dev);
252 }
253 
native_tty_uart_irq_update(const struct device * dev)254 static int native_tty_uart_irq_update(const struct device *dev)
255 {
256 	ARG_UNUSED(dev);
257 	return 1;
258 }
259 
native_tty_uart_irq_handler(const struct device * dev)260 static void native_tty_uart_irq_handler(const struct device *dev)
261 {
262 	struct native_tty_data *data = dev->data;
263 
264 	if (data->callback) {
265 		data->callback(dev, data->cb_data);
266 	} else {
267 		WARN("No callback!\n");
268 	}
269 }
270 
271 /*
272  * Emulate uart interrupts using a polling thread
273  */
native_tty_uart_irq_function(void * arg1,void * arg2,void * arg3)274 void native_tty_uart_irq_function(void *arg1, void *arg2, void *arg3)
275 {
276 	ARG_UNUSED(arg2);
277 	ARG_UNUSED(arg3);
278 	struct device *dev = (struct device *)arg1;
279 	struct native_tty_data *data = dev->data;
280 
281 	while (1) {
282 		if (data->rx_irq_enabled) {
283 			int ret = native_tty_poll_bottom(data->fd);
284 
285 			if (ret == 1) {
286 				native_tty_uart_irq_handler(dev);
287 			} else if (ret < 0) {
288 				WARN("Poll returned error %d\n", ret);
289 			} else {
290 				k_sleep(K_MSEC(1));
291 			}
292 		}
293 		if (data->tx_irq_enabled) {
294 			native_tty_uart_irq_handler(dev);
295 		}
296 		if (data->tx_irq_enabled == false && data->rx_irq_enabled == false) {
297 			k_sleep(K_MSEC(10));
298 		}
299 	}
300 }
301 
native_tty_uart_irq_callback_set(const struct device * dev,uart_irq_callback_user_data_t cb,void * cb_data)302 static void native_tty_uart_irq_callback_set(const struct device *dev,
303 					     uart_irq_callback_user_data_t cb,
304 					     void *cb_data)
305 {
306 	struct native_tty_data *data = dev->data;
307 
308 	data->callback = cb;
309 	data->cb_data = cb_data;
310 }
311 
native_tty_irq_init(const struct device * dev)312 static void native_tty_irq_init(const struct device *dev)
313 {
314 	/* Create a thread which will wait for data - replacement for IRQ */
315 	k_thread_create(&rx_thread, rx_stack, K_KERNEL_STACK_SIZEOF(rx_stack),
316 			native_tty_uart_irq_function,
317 			(void *)dev, NULL, NULL,
318 			K_HIGHEST_THREAD_PRIO, 0, K_NO_WAIT);
319 }
320 #endif /* CONFIG_UART_INTERRUPT_DRIVEN */
321 
native_tty_serial_init(const struct device * dev)322 static int native_tty_serial_init(const struct device *dev)
323 {
324 	struct native_tty_data *data = dev->data;
325 	struct uart_config uart_config = ((struct native_tty_config *)dev->config)->uart_config;
326 
327 	/* Default value for cmd_serial_port is NULL, this is due to the set 's' type in
328 	 * command line opts. If it is anything else then it was configured via command
329 	 * line.
330 	 */
331 	if (data->cmd_serial_port) {
332 		data->serial_port = data->cmd_serial_port;
333 	}
334 
335 	/* Default value for cmd_baudrate is UINT32_MAX, this is due to the set 'u' type in
336 	 * command line opts. If it is anything else then it was configured via command
337 	 * line.
338 	 */
339 	if (data->cmd_baudrate != UINT32_MAX) {
340 		uart_config.baudrate = data->cmd_baudrate;
341 	}
342 
343 	/* Serial port needs to be set either in the devicetree or provided via command line
344 	 * opts, if that is not the case, then abort.
345 	 */
346 	if (!data->serial_port) {
347 		ERROR("%s: path to the serial port was not set.\n", dev->name);
348 	}
349 
350 	/* Try to open a serial port as with read/write access, also prevent serial port
351 	 * from becoming the controlling terminal.
352 	 */
353 
354 	data->fd = native_tty_open_tty_bottom(data->serial_port);
355 
356 	if (native_tty_configure(dev, &uart_config)) {
357 		ERROR("%s: could not configure serial port %s\n", dev->name, data->serial_port);
358 	}
359 
360 	posix_print_trace("%s connected to the serial port: %s\n", dev->name, data->serial_port);
361 
362 #ifdef CONFIG_UART_INTERRUPT_DRIVEN
363 	/* Start irq emulation thread */
364 	native_tty_irq_init(dev);
365 #endif
366 	return 0;
367 }
368 
369 static DEVICE_API(uart, native_tty_uart_driver_api) = {
370 	.poll_out = native_tty_uart_poll_out,
371 	.poll_in = native_tty_uart_poll_in,
372 #ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE
373 	.configure = native_tty_configure,
374 #endif
375 #ifdef CONFIG_UART_INTERRUPT_DRIVEN
376 	.fifo_fill        = native_tty_uart_fifo_fill,
377 	.fifo_read        = native_tty_uart_fifo_read,
378 	.irq_tx_enable    = native_tty_uart_irq_tx_enable,
379 	.irq_tx_disable	  = native_tty_uart_irq_tx_disable,
380 	.irq_tx_ready     = native_tty_uart_irq_tx_ready,
381 	.irq_tx_complete  = native_tty_uart_irq_tx_complete,
382 	.irq_rx_enable    = native_tty_uart_irq_rx_enable,
383 	.irq_rx_disable   = native_tty_uart_irq_rx_disable,
384 	.irq_rx_ready     = native_tty_uart_irq_rx_ready,
385 	.irq_is_pending   = native_tty_uart_irq_is_pending,
386 	.irq_update       = native_tty_uart_irq_update,
387 	.irq_callback_set = native_tty_uart_irq_callback_set,
388 #endif
389 };
390 
391 #define NATIVE_TTY_INSTANCE(inst)                                                                  \
392 	static const struct native_tty_config native_tty_##inst##_cfg = {                          \
393 		.uart_config =                                                                     \
394 			{                                                                          \
395 				.data_bits = UART_CFG_DATA_BITS_8,                                 \
396 				.flow_ctrl = UART_CFG_FLOW_CTRL_NONE,                              \
397 				.parity = UART_CFG_PARITY_NONE,                                    \
398 				.stop_bits = UART_CFG_STOP_BITS_1,                                 \
399 				.baudrate = DT_INST_PROP(inst, current_speed),                     \
400 			},                                                                         \
401 	};                                                                                         \
402                                                                                                    \
403 	static struct native_tty_data native_tty_##inst##_data = {                                 \
404 		.serial_port = DT_INST_PROP_OR(inst, serial_port, NULL),                           \
405 	};                                                                                         \
406                                                                                                    \
407 	DEVICE_DT_INST_DEFINE(inst, native_tty_serial_init, NULL, &native_tty_##inst##_data,       \
408 			      &native_tty_##inst##_cfg, NATIVE_TTY_INIT_LEVEL, 55,                 \
409 			      &native_tty_uart_driver_api);
410 
411 DT_INST_FOREACH_STATUS_OKAY(NATIVE_TTY_INSTANCE);
412 
413 #define INST_NAME(inst) DEVICE_DT_NAME(DT_DRV_INST(inst))
414 
415 #define NATIVE_TTY_COMMAND_LINE_OPTS(inst)                                                         \
416 	{                                                                                          \
417 		.option = INST_NAME(inst) "_port",						   \
418 		.name = "\"serial_port\"",                                                         \
419 		.type = 's',                                                                       \
420 		.dest = &native_tty_##inst##_data.cmd_serial_port,                                 \
421 		.descript = "Set a serial port for " INST_NAME(inst) " uart device, "		   \
422 		"overriding the one in devicetree.",						   \
423 	},                                                                                         \
424 	{											   \
425 		.option = INST_NAME(inst) "_baud",						   \
426 		.name = "baudrate",								   \
427 		.type = 'u',									   \
428 		.dest = &native_tty_##inst##_data.cmd_baudrate,					   \
429 		.descript = "Set a baudrate for " INST_NAME(inst) " device, overriding the "	   \
430 		"baudrate of " STRINGIFY(DT_INST_PROP(inst, current_speed))			   \
431 		"set in the devicetree.",							   \
432 	},
433 
434 /**
435  * @brief Adds command line options for setting serial port and baud rate for each uart
436  * device.
437  */
native_tty_add_serial_options(void)438 static void native_tty_add_serial_options(void)
439 {
440 	static struct args_struct_t opts[] = {
441 		DT_INST_FOREACH_STATUS_OKAY(NATIVE_TTY_COMMAND_LINE_OPTS) ARG_TABLE_ENDMARKER};
442 
443 	native_add_command_line_opts(opts);
444 }
445 
446 #define NATIVE_TTY_CLEANUP(inst)                                                                   \
447 	if (native_tty_##inst##_data.fd != 0) {                                                    \
448 		nsi_host_close(native_tty_##inst##_data.fd);                                       \
449 	}
450 
451 /**
452  * @brief Cleans up any open serial ports on the exit.
453  */
native_tty_cleanup_uart(void)454 static void native_tty_cleanup_uart(void)
455 {
456 	DT_INST_FOREACH_STATUS_OKAY(NATIVE_TTY_CLEANUP);
457 }
458 
459 NATIVE_TASK(native_tty_add_serial_options, PRE_BOOT_1, 11);
460 NATIVE_TASK(native_tty_cleanup_uart, ON_EXIT, 99);
461