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