/
uart_native_posix.c
335 lines (286 loc) · 8.18 KB
/
uart_native_posix.c
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
/*
* Copyright (c) 2018, Oticon A/S
*
* SPDX-License-Identifier: Apache-2.0
*/
#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 "uart.h"
#include "cmdline.h" /* native_posix command line options header */
#include "soc.h"
/*
* UART driver for POSIX ARCH based boards.
* 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(struct device *dev, unsigned char *p_char);
static int np_uart_tty_poll_in(struct device *dev, unsigned char *p_char);
static unsigned char np_uart_poll_out(struct device *dev,
unsigned char out_char);
static bool auto_attach;
static const char default_cmd[] = CONFIG_NATIVE_UART_AUTOATTACH_DEFAULT_CMD;
static char *auto_attach_cmd;
static struct uart_driver_api np_uart_driver_api = {
.poll_out = np_uart_poll_out,
.poll_in = np_uart_tty_poll_in,
};
struct native_uart_status {
int out_fd; /* File descriptor used for output */
int in_fd; /* File descriptor used for input */
};
#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)];
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(void)
{
int master_pty;
char *slave_pty_name;
struct termios ter;
struct winsize win;
int err_nbr;
int ret;
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 */
fcntl(master_pty, F_SETFL, fcntl(master_pty, F_GETFL) | O_NONBLOCK);
/*
* 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("UART connected to pseudotty: %s\n", slave_pty_name);
if (auto_attach) {
attach_to_tty(slave_pty_name);
}
return master_pty;
}
/**
* @brief Initialize the native_posix serial port
*
* @param dev UART device struct
*
* @return 0 (if it fails catastrophically, the execution is terminated)
*/
static int np_uart_init(struct device *dev)
{
struct native_uart_status *d;
d = (struct native_uart_status *)dev->driver_data;
if (IS_ENABLED(CONFIG_NATIVE_UART_0_ON_OWN_PTY)) {
int tty_fn = open_tty();
d->in_fd = tty_fn;
d->out_fd = tty_fn;
np_uart_driver_api.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.poll_in = np_uart_stdin_poll_in;
if (isatty(STDIN_FILENO)) {
WARN("The UART dirver 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"
);
}
}
dev->driver_api = &np_uart_driver_api;
return 0;
}
/*
* @brief Output a character towards the serial port
*
* @param dev UART device struct
* @param out_char Character to send.
*/
static unsigned char np_uart_poll_out(struct device *dev,
unsigned char out_char)
{
int ret;
struct native_uart_status *d;
d = (struct native_uart_status *)dev->driver_data;
ret = write(d->out_fd, &out_char, 1);
if (ret != 1) {
WARN("%s: a character could not be output\n", __func__);
}
return out_char;
}
/**
* @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(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->driver_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(struct device *dev, unsigned char *p_char)
{
int n = -1;
int in_f = ((struct native_uart_status *)dev->driver_data)->in_fd;
n = read(in_f, p_char, 1);
if (n == -1) {
return -1;
}
return 0;
}
static struct native_uart_status native_uart_status_0;
DEVICE_INIT(uart_native_posix0,
CONFIG_UART_NATIVE_POSIX_PORT_0_NAME, &np_uart_init,
(void *)&native_uart_status_0, NULL,
PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);
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, NULL,
"Command used to automatically attach to the terminal, by "
"default: '" CONFIG_NATIVE_UART_AUTOATTACH_DEFAULT_CMD "'"},
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);
}
}
}
NATIVE_TASK(np_add_uart_options, PRE_BOOT_1, 11);
NATIVE_TASK(np_cleanup_uart, ON_EXIT, 99);