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