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