1 /*
2 * Copyright (c) 2018, Oticon A/S
3 * Copyright (c) 2023, Nordic Semiconductor ASA
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #undef _XOPEN_SOURCE
9 /* Note: This is used only for interaction with the host C library, and is therefore exempt of
10 * coding guidelines rule A.4&5 which applies to the embedded code using embedded libraries
11 */
12 #define _XOPEN_SOURCE 600
13
14 #include <stdbool.h>
15 #include <errno.h>
16 #include <stddef.h>
17 #include <stdlib.h>
18 #include <stdio.h>
19 #include <string.h>
20 #include <pty.h>
21 #include <fcntl.h>
22 #include <poll.h>
23 #include <unistd.h>
24 #include <poll.h>
25 #include <nsi_tracing.h>
26
27 #define ERROR nsi_print_error_and_exit
28 #define WARN nsi_print_warning
29
30 /**
31 * @brief Poll the device for input.
32 *
33 * @param in_f Input file descriptor
34 * @param p_char Pointer to character.
35 * @param len Maximum number of characters to read.
36 *
37 * @retval >0 Number of characters actually read
38 * @retval -1 If no character was available to read
39 * @retval -2 if the stdin is disconnected
40 */
np_uart_stdin_read_bottom(int in_f,unsigned char * p_char,int len)41 int np_uart_stdin_read_bottom(int in_f, unsigned char *p_char, int len)
42 {
43 int n = -1;
44 int ready;
45
46 struct pollfd fds = {.fd = in_f, .events = POLLIN};
47
48 ready = poll(&fds, 1, 0);
49
50 if (ready == 0) {
51 return -1;
52 } else if (ready == -1) {
53 ERROR("%s: Error on poll ()\n", __func__);
54 }
55
56 if (len == 0) {
57 return 0;
58 }
59
60 n = read(in_f, p_char, len);
61
62 if (n == 0) {
63 /* Attempting to read > 0 but getting 0 characters back
64 * indicates we reached EOF
65 */
66 return -2;
67 } else {
68 return n;
69 }
70 }
71
72 /**
73 * @brief Check if the output descriptor has something connected to the slave side
74 *
75 * @param fd file number
76 *
77 * @retval 0 Nothing connected yet
78 * @retval 1 Something connected to the slave side
79 */
np_uart_slave_connected(int fd)80 int np_uart_slave_connected(int fd)
81 {
82 struct pollfd pfd = { .fd = fd, .events = POLLHUP };
83 int ret;
84
85 ret = poll(&pfd, 1, 0);
86 if (ret == -1) {
87 int err = errno;
88 /*
89 * Possible errors are:
90 * * EINTR :A signal was received => ok
91 * * EFAULT and EINVAL: parameters/programming error
92 * * ENOMEM no RAM left
93 */
94 if (err != EINTR) {
95 ERROR("%s: unexpected error during poll, errno=%i,%s\n",
96 __func__, err, strerror(err));
97 }
98 }
99 if (!(pfd.revents & POLLHUP)) {
100 /* There is now a reader on the slave side */
101 return 1;
102 }
103 return 0;
104 }
105
106 /**
107 * Attempt to connect a terminal emulator to the slave side of the pty
108 * If -attach_uart_cmd=<cmd> is provided as a command line option, <cmd> will be
109 * used. Otherwise, the default command,
110 * CONFIG_NATIVE_UART_AUTOATTACH_DEFAULT_CMD, will be used instead
111 */
attach_to_pty(const char * slave_pty,const char * auto_attach_cmd)112 static void attach_to_pty(const char *slave_pty, const char *auto_attach_cmd)
113 {
114 char command[strlen(auto_attach_cmd) + strlen(slave_pty) + 1];
115
116 sprintf(command, auto_attach_cmd, slave_pty);
117
118 int ret = system(command);
119
120 if (ret != 0) {
121 WARN("Could not attach to the UART with \"%s\"\n", command);
122 WARN("The command returned %i\n", WEXITSTATUS(ret));
123 }
124 }
125 /**
126 * Attempt to allocate and open a new pseudoterminal
127 *
128 * Returns the file descriptor of the master side
129 * If auto_attach was set, it will also attempt to connect a new terminal
130 * emulator to its slave side.
131 */
np_uart_open_pty(const char * uart_name,const char * auto_attach_cmd,bool do_auto_attach,bool wait_pts)132 int np_uart_open_pty(const char *uart_name, const char *auto_attach_cmd,
133 bool do_auto_attach, bool wait_pts)
134 {
135 int master_pty;
136 char *slave_pty_name;
137 struct termios ter;
138 int err_nbr;
139 int ret;
140 int flags;
141
142 master_pty = posix_openpt(O_RDWR | O_NOCTTY);
143 if (master_pty == -1) {
144 ERROR("Could not open a new PTY for the UART\n");
145 }
146 ret = grantpt(master_pty);
147 if (ret == -1) {
148 err_nbr = errno;
149 close(master_pty);
150 ERROR("Could not grant access to the slave PTY side (%i)\n",
151 err_nbr);
152 }
153 ret = unlockpt(master_pty);
154 if (ret == -1) {
155 err_nbr = errno;
156 close(master_pty);
157 ERROR("Could not unlock the slave PTY side (%i)\n", err_nbr);
158 }
159 slave_pty_name = ptsname(master_pty);
160 if (slave_pty_name == NULL) {
161 err_nbr = errno;
162 close(master_pty);
163 ERROR("Error getting slave PTY device name (%i)\n", err_nbr);
164 }
165 /* Set the master PTY as non blocking */
166 flags = fcntl(master_pty, F_GETFL);
167 if (flags == -1) {
168 err_nbr = errno;
169 close(master_pty);
170 ERROR("Could not read the master PTY file status flags (%i)\n",
171 err_nbr);
172 }
173
174 ret = fcntl(master_pty, F_SETFL, flags | O_NONBLOCK);
175 if (ret == -1) {
176 err_nbr = errno;
177 close(master_pty);
178 ERROR("Could not set the master PTY as non-blocking (%i)\n",
179 err_nbr);
180 }
181
182 (void) err_nbr;
183
184 /*
185 * Set terminal in "raw" mode:
186 * Not canonical (no line input)
187 * No signal generation from Ctr+{C|Z..}
188 * No echoing, no input or output processing
189 * No replacing of NL or CR
190 * No flow control
191 */
192 ret = tcgetattr(master_pty, &ter);
193 if (ret == -1) {
194 ERROR("Could not read terminal driver settings\n");
195 }
196 ter.c_cc[VMIN] = 0;
197 ter.c_cc[VTIME] = 0;
198 ter.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO);
199 ter.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR | INPCK
200 | ISTRIP | IXON | PARMRK);
201 ter.c_oflag &= ~OPOST;
202 ret = tcsetattr(master_pty, TCSANOW, &ter);
203 if (ret == -1) {
204 ERROR("Could not change terminal driver settings\n");
205 }
206
207 nsi_print_trace("%s connected to pseudotty: %s\n",
208 uart_name, slave_pty_name);
209
210 if (wait_pts) {
211 /*
212 * This trick sets the HUP flag on the pty master, making it
213 * possible to detect a client connection using poll.
214 * The connection of the client would cause the HUP flag to be
215 * cleared, and in turn set again at disconnect.
216 */
217 ret = open(slave_pty_name, O_RDWR | O_NOCTTY);
218 if (ret == -1) {
219 err_nbr = errno;
220 ERROR("%s: Could not open terminal from the slave side (%i,%s)\n",
221 __func__, err_nbr, strerror(err_nbr));
222 }
223 ret = close(ret);
224 if (ret == -1) {
225 err_nbr = errno;
226 ERROR("%s: Could not close terminal from the slave side (%i,%s)\n",
227 __func__, err_nbr, strerror(err_nbr));
228 }
229 }
230 if (do_auto_attach) {
231 attach_to_pty(slave_pty_name, auto_attach_cmd);
232 }
233
234 return master_pty;
235 }
236
np_uart_pty_get_stdin_fileno(void)237 int np_uart_pty_get_stdin_fileno(void)
238 {
239 return STDIN_FILENO;
240 }
241
np_uart_pty_get_stdout_fileno(void)242 int np_uart_pty_get_stdout_fileno(void)
243 {
244 return STDOUT_FILENO;
245 }
246