Linux Audio

Check our new training course

Embedded Linux Audio

Check our new training course
with Creative Commons CC-BY-SA
lecture materials

Bootlin logo

Elixir Cross Referencer

Loading...
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
/*
 * Copyright (c) 2018, Oticon A/S
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT zephyr_native_posix_uart

#include <errno.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pty.h>
#include <fcntl.h>
#include <sys/select.h>
#include <unistd.h>
#include <poll.h>

#include <drivers/uart.h>
#include "cmdline.h" /* native_posix command line options header */
#include "soc.h"

/*
 * UART driver for POSIX ARCH based boards.
 * It can support up to two UARTs.
 *
 * For the first UART:
 *
 * It can either be connected to the process STDIN+STDOUT
 * OR
 * to a dedicated pseudo terminal
 *
 * The 2nd option is the recommended one for interactive use, as the pseudo
 * terminal driver will be configured in "raw" mode, and will therefore behave
 * more like a real UART.
 *
 * When connected to its own pseudo terminal, it may also auto attach a terminal
 * emulator to it, if set so from command line.
 */

static int np_uart_stdin_poll_in(const struct device *dev,
				 unsigned char *p_char);
static int np_uart_tty_poll_in(const struct device *dev,
			       unsigned char *p_char);
static void np_uart_poll_out(const struct device *dev,
				      unsigned char out_char);

static bool auto_attach;
static bool wait_pts;
static const char default_cmd[] = CONFIG_NATIVE_UART_AUTOATTACH_DEFAULT_CMD;
static char *auto_attach_cmd;

struct native_uart_status {
	int out_fd; /* File descriptor used for output */
	int in_fd; /* File descriptor used for input */
};

static struct native_uart_status native_uart_status_0;

static struct uart_driver_api np_uart_driver_api_0 = {
	.poll_out = np_uart_poll_out,
	.poll_in = np_uart_tty_poll_in,
};

#if defined(CONFIG_UART_NATIVE_POSIX_PORT_1_ENABLE)
static struct native_uart_status native_uart_status_1;

static struct uart_driver_api np_uart_driver_api_1 = {
	.poll_out = np_uart_poll_out,
	.poll_in = np_uart_tty_poll_in,
};
#endif /* CONFIG_UART_NATIVE_POSIX_PORT_1_ENABLE */

#define ERROR posix_print_error_and_exit
#define WARN posix_print_warning

/**
 * Attempt to connect a terminal emulator to the slave side of the pty
 * If -attach_uart_cmd=<cmd> is provided as a command line option, <cmd> will be
 * used. Otherwise, the default command,
 * CONFIG_NATIVE_UART_AUTOATTACH_DEFAULT_CMD, will be used instead
 */
static void attach_to_tty(const char *slave_tty)
{
	if (auto_attach_cmd == NULL) {
		auto_attach_cmd = (char *)default_cmd;
	}
	char command[strlen(auto_attach_cmd) + strlen(slave_tty) + 1];

	sprintf(command, auto_attach_cmd, slave_tty);

	int ret = system(command);

	if (ret != 0) {
		WARN("Could not attach to the UART with \"%s\"\n", command);
		WARN("The command returned %i\n", WEXITSTATUS(ret));
	}
}

/**
 * Attempt to allocate and open a new pseudoterminal
 *
 * Returns the file descriptor of the master side
 * If auto_attach was set, it will also attempt to connect a new terminal
 * emulator to its slave side.
 */
static int open_tty(struct native_uart_status *driver_data,
		    const char *uart_name,
		    bool do_auto_attach)
{
	int master_pty;
	char *slave_pty_name;
	struct termios ter;
	struct winsize win;
	int err_nbr;
	int ret;
	int flags;

	win.ws_col = 80;
	win.ws_row = 24;

	master_pty = posix_openpt(O_RDWR | O_NOCTTY);
	if (master_pty == -1) {
		ERROR("Could not open a new TTY for the UART\n");
	}
	ret = grantpt(master_pty);
	if (ret == -1) {
		err_nbr = errno;
		close(master_pty);
		ERROR("Could not grant access to the slave PTY side (%i)\n",
			errno);
	}
	ret = unlockpt(master_pty);
	if (ret == -1) {
		err_nbr = errno;
		close(master_pty);
		ERROR("Could not unlock the slave PTY side (%i)\n", errno);
	}
	slave_pty_name = ptsname(master_pty);
	if (slave_pty_name == NULL) {
		err_nbr = errno;
		close(master_pty);
		ERROR("Error getting slave PTY device name (%i)\n", errno);
	}
	/* Set the master PTY as non blocking */
	flags = fcntl(master_pty, F_GETFL);
	if (flags == -1) {
		err_nbr = errno;
		close(master_pty);
		ERROR("Could not read the master PTY file status flags (%i)\n",
			errno);
	}

	ret = fcntl(master_pty, F_SETFL, flags | O_NONBLOCK);
	if (ret == -1) {
		err_nbr = errno;
		close(master_pty);
		ERROR("Could not set the master PTY as non-blocking (%i)\n",
			errno);
	}

	/*
	 * Set terminal in "raw" mode:
	 *  Not canonical (no line input)
	 *  No signal generation from Ctr+{C|Z..}
	 *  No echoing, no input or output processing
	 *  No replacing of NL or CR
	 *  No flow control
	 */
	ret = tcgetattr(master_pty, &ter);
	if (ret == -1) {
		ERROR("Could not read terminal driver settings\n");
	}
	ter.c_cc[VMIN] = 0;
	ter.c_cc[VTIME] = 0;
	ter.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO);
	ter.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR | INPCK
			 | ISTRIP | IXON | PARMRK);
	ter.c_oflag &= ~OPOST;
	ret = tcsetattr(master_pty, TCSANOW, &ter);
	if (ret == -1) {
		ERROR("Could not change terminal driver settings\n");
	}

	posix_print_trace("%s connected to pseudotty: %s\n",
			  uart_name, slave_pty_name);

	if (wait_pts) {
		/*
		 * This trick sets the HUP flag on the tty master, making it
		 * possible to detect a client connection using poll.
		 * The connection of the client would cause the HUP flag to be
		 * cleared, and in turn set again at disconnect.
		 */
		close(open(slave_pty_name, O_RDWR | O_NOCTTY));
	}
	if (do_auto_attach) {
		attach_to_tty(slave_pty_name);
	}

	return master_pty;
}

/**
 * @brief Initialize the first native_posix serial port
 *
 * @param dev UART_0 device struct
 *
 * @return 0 (if it fails catastrophically, the execution is terminated)
 */
static int np_uart_0_init(const struct device *dev)
{
	struct native_uart_status *d;

	d = (struct native_uart_status *)dev->data;

	if (IS_ENABLED(CONFIG_NATIVE_UART_0_ON_OWN_PTY)) {
		int tty_fn = open_tty(d, DT_INST_LABEL(0),
				      auto_attach);

		d->in_fd = tty_fn;
		d->out_fd = tty_fn;
		np_uart_driver_api_0.poll_in = np_uart_tty_poll_in;
	} else { /* NATIVE_UART_0_ON_STDINOUT */
		d->in_fd  = STDIN_FILENO;
		d->out_fd = STDOUT_FILENO;
		np_uart_driver_api_0.poll_in = np_uart_stdin_poll_in;

		if (isatty(STDIN_FILENO)) {
			WARN("The UART driver has been configured to map to the"
			     " process stdin&out (NATIVE_UART_0_ON_STDINOUT), "
			     "but stdin seems to be left attached to the shell."
			     " This will most likely NOT behave as you want it "
			     "to. This option is NOT meant for interactive use "
			     "but for piping/feeding from/to files to the UART"
			     );
		}
	}

	return 0;
}

#if defined(CONFIG_UART_NATIVE_POSIX_PORT_1_ENABLE)
/*
 * Initialize the 2nd UART port.
 * This port will be always attached to its own new pseudoterminal.
 */
static int np_uart_1_init(const struct device *dev)
{
	struct native_uart_status *d;
	int tty_fn;

	d = (struct native_uart_status *)dev->data;

	tty_fn = open_tty(d, DT_INST_LABEL(1), false);

	d->in_fd = tty_fn;
	d->out_fd = tty_fn;

	return 0;
}
#endif

/*
 * @brief Output a character towards the serial port
 *
 * @param dev UART device struct
 * @param out_char Character to send.
 */
static void np_uart_poll_out(const struct device *dev,
				      unsigned char out_char)
{
	int ret;
	struct native_uart_status *d = (struct native_uart_status *)dev->data;

	if (wait_pts) {
		struct pollfd pfd = { .fd = d->out_fd, .events = POLLHUP };

		while (1) {
			poll(&pfd, 1, 0);
			if (!(pfd.revents & POLLHUP)) {
				/* There is now a reader on the slave side */
				break;
			}
			k_sleep(K_MSEC(100));
		}
	}

	/* The return value of write() cannot be ignored (there is a warning)
	 * but we do not need the return value for anything.
	 */
	ret = write(d->out_fd, &out_char, 1);
}

/**
 * @brief Poll the device for input.
 *
 * @param dev UART device structure.
 * @param p_char Pointer to character.
 *
 * @retval 0 If a character arrived and was stored in p_char
 * @retval -1 If no character was available to read
 */
static int np_uart_stdin_poll_in(const struct device *dev,
				 unsigned char *p_char)
{
	static bool disconnected;

	if (disconnected || feof(stdin)) {
		/*
		 * The stdinput is fed from a file which finished or the user
		 * pressed Crtl+D
		 */
		disconnected = true;
		return -1;
	}

	int n = -1;
	int in_f = ((struct native_uart_status *)dev->data)->in_fd;

	int ready;
	fd_set readfds;
	static struct timeval timeout; /* just zero */

	FD_ZERO(&readfds);
	FD_SET(in_f, &readfds);

	ready = select(in_f+1, &readfds, NULL, NULL, &timeout);

	if (ready == 0) {
		return -1;
	} else if (ready == -1) {
		ERROR("%s: Error on select ()\n", __func__);
	}

	n = read(in_f, p_char, 1);
	if (n == -1) {
		return -1;
	}

	return 0;
}

/**
 * @brief Poll the device for input.
 *
 * @param dev UART device structure.
 * @param p_char Pointer to character.
 *
 * @retval 0 If a character arrived and was stored in p_char
 * @retval -1 If no character was available to read
 */
static int np_uart_tty_poll_in(const struct device *dev,
			       unsigned char *p_char)
{
	int n = -1;
	int in_f = ((struct native_uart_status *)dev->data)->in_fd;

	n = read(in_f, p_char, 1);
	if (n == -1) {
		return -1;
	}
	return 0;
}

DEVICE_DT_INST_DEFINE(0,
	    &np_uart_0_init, NULL,
	    (void *)&native_uart_status_0, NULL,
	    PRE_KERNEL_1, CONFIG_SERIAL_INIT_PRIORITY,
	    &np_uart_driver_api_0);

#if defined(CONFIG_UART_NATIVE_POSIX_PORT_1_ENABLE)
DEVICE_DT_INST_DEFINE(1,
	    &np_uart_1_init, NULL,
	    (void *)&native_uart_status_1, NULL,
	    PRE_KERNEL_1, CONFIG_SERIAL_INIT_PRIORITY,
	    &np_uart_driver_api_1);
#endif /* CONFIG_UART_NATIVE_POSIX_PORT_1_ENABLE */

static void auto_attach_cmd_cb(char *argv, int offset)
{
	ARG_UNUSED(argv);
	ARG_UNUSED(offset);

	auto_attach = true;
}

static void np_add_uart_options(void)
{
	if (!IS_ENABLED(CONFIG_NATIVE_UART_0_ON_OWN_PTY)) {
		return;
	}

	static struct args_struct_t uart_options[] = {
		/*
		 * Fields:
		 * manual, mandatory, switch,
		 * option_name, var_name ,type,
		 * destination, callback,
		 * description
		 */
		{false, false, true,
		"attach_uart", "", 'b',
		(void *)&auto_attach, NULL,
		"Automatically attach to the UART terminal"},
		{false, false, false,
		"attach_uart_cmd", "\"cmd\"", 's',
		(void *)&auto_attach_cmd, auto_attach_cmd_cb,
		"Command used to automatically attach to the terminal"
		"(implies auto_attach), by "
		"default: '" CONFIG_NATIVE_UART_AUTOATTACH_DEFAULT_CMD "'"},
		IF_ENABLED(CONFIG_UART_NATIVE_WAIT_PTS_READY_ENABLE, (
			{false, false, true,
			"wait_uart", "", 'b',
			(void *)&wait_pts, NULL,
			"Hold writes to the uart/pts until a client is "
			"connected/ready"},)
		)
		ARG_TABLE_ENDMARKER
	};

	native_add_command_line_opts(uart_options);
}

static void np_cleanup_uart(void)
{
	if (IS_ENABLED(CONFIG_NATIVE_UART_0_ON_OWN_PTY)) {
		if (native_uart_status_0.in_fd != 0) {
			close(native_uart_status_0.in_fd);
		}
	}

#if defined(CONFIG_UART_NATIVE_POSIX_PORT_1_ENABLE)
	if (native_uart_status_1.in_fd != 0) {
		close(native_uart_status_1.in_fd);
	}
#endif
}

NATIVE_TASK(np_add_uart_options, PRE_BOOT_1, 11);
NATIVE_TASK(np_cleanup_uart, ON_EXIT, 99);