1 /**
2 * @brief "Bottom" of native tty uart driver
3 *
4 * Copyright (c) 2023 Marko Sagadin
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #include "uart_native_tty_bottom.h"
9
10 #include <errno.h>
11 #include <stdio.h>
12 #include <string.h>
13 #include <fcntl.h>
14 #include <poll.h>
15 #include <termios.h>
16 #include <unistd.h>
17
18 #include <nsi_errno.h>
19 #include <nsi_tracing.h>
20
21 #define WARN(...) nsi_print_warning(__VA_ARGS__)
22 #define ERROR(...) nsi_print_error_and_exit(__VA_ARGS__)
23
24 #define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
25
26 struct baudrate_termios_pair {
27 int baudrate;
28 speed_t termios_baudrate;
29 };
30
31 /**
32 * @brief Lookup table for mapping the baud rate to the macro understood by termios.
33 */
34 static const struct baudrate_termios_pair baudrate_lut[] = {
35 {1200, B1200}, {1800, B1800}, {2400, B2400}, {4800, B4800},
36 {9600, B9600}, {19200, B19200}, {38400, B38400}, {57600, B57600},
37 {115200, B115200}, {230400, B230400}, {460800, B460800}, {500000, B500000},
38 {576000, B576000}, {921600, B921600}, {1000000, B1000000}, {1152000, B1152000},
39 {1500000, B1500000}, {2000000, B2000000}, {2500000, B2500000}, {3000000, B3000000},
40 {3500000, B3500000}, {4000000, B4000000},
41 };
42
43 /**
44 * @brief Set given termios to defaults appropriate for communicating with serial port devices.
45 *
46 * @param ter
47 */
native_tty_termios_defaults_set(struct termios * ter)48 static inline void native_tty_termios_defaults_set(struct termios *ter)
49 {
50 /* Set terminal in "serial" mode:
51 * - Not canonical (no line input)
52 * - No signal generation from Ctr+{C|Z..}
53 * - No echoing
54 */
55 ter->c_lflag &= ~(ICANON | ISIG | ECHO);
56
57 /* No special interpretation of output bytes.
58 * No conversion of newline to carriage return/line feed.
59 */
60 ter->c_oflag &= ~(OPOST | ONLCR);
61
62 /* No software flow control. */
63 ter->c_iflag &= ~(IXON | IXOFF | IXANY);
64
65 /* No blocking, return immediately with what is available. */
66 ter->c_cc[VMIN] = 0;
67 ter->c_cc[VTIME] = 0;
68
69 /* No special handling of bytes on receive. */
70 ter->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL);
71
72 /* - Enable reading data and ignore control lines */
73 ter->c_cflag |= CREAD | CLOCAL;
74 }
75
76 /**
77 * @brief Set the baud rate speed in the termios structure
78 *
79 * @param ter
80 * @param baudrate
81 */
native_tty_baud_speed_set(struct termios * ter,int baudrate)82 static inline void native_tty_baud_speed_set(struct termios *ter, int baudrate)
83 {
84 for (int i = 0; i < ARRAY_SIZE(baudrate_lut); i++) {
85 if (baudrate_lut[i].baudrate == baudrate) {
86 cfsetospeed(ter, baudrate_lut[i].termios_baudrate);
87 cfsetispeed(ter, baudrate_lut[i].termios_baudrate);
88 return;
89 }
90 }
91 ERROR("Could not set baudrate, as %d is not supported.\n", baudrate);
92 }
93
94 /**
95 * @brief Get the baud rate speed from the termios structure
96 *
97 * @param ter
98 * @param baudrate
99 */
native_tty_baud_speed_get(const struct termios * ter,uint32_t * baudrate)100 static inline void native_tty_baud_speed_get(const struct termios *ter, uint32_t *baudrate)
101 {
102 speed_t ispeed = cfgetispeed(ter);
103 speed_t ospeed = cfgetospeed(ter);
104
105 if (ispeed != ospeed) {
106 ERROR("Input and output baud rates differ: %d vs %d\n", ispeed, ospeed);
107 }
108
109 for (int i = 0; i < ARRAY_SIZE(baudrate_lut); i++) {
110 if (baudrate_lut[i].termios_baudrate == ispeed) {
111 *baudrate = baudrate_lut[i].baudrate;
112 return;
113 }
114 }
115
116 ERROR("Unsupported termios baudrate: %d\n", ispeed);
117 }
118
119 /**
120 * @brief Set parity setting in the termios structure
121 *
122 * @param ter
123 * @param parity
124 */
native_tty_baud_parity_set(struct termios * ter,enum native_tty_bottom_parity parity)125 static inline void native_tty_baud_parity_set(struct termios *ter,
126 enum native_tty_bottom_parity parity)
127 {
128 switch (parity) {
129 case NTB_PARITY_NONE:
130 ter->c_cflag &= ~PARENB;
131 break;
132 case NTB_PARITY_ODD:
133 ter->c_cflag |= PARENB;
134 ter->c_cflag |= PARODD;
135 break;
136 case NTB_PARITY_EVEN:
137 ter->c_cflag |= PARENB;
138 ter->c_cflag &= ~PARODD;
139 break;
140 default:
141 /* Parity options mark and space are not supported on this driver. */
142 ERROR("Could not set parity.\n");
143 }
144 }
145
146 /**
147 * @brief Get the parity setting from the termios structure
148 *
149 * @param ter
150 * @param parity
151 */
native_tty_baud_parity_get(const struct termios * ter,enum native_tty_bottom_parity * parity)152 static inline void native_tty_baud_parity_get(const struct termios *ter,
153 enum native_tty_bottom_parity *parity)
154 {
155 if ((ter->c_cflag & PARENB) == 0) {
156 *parity = NTB_PARITY_NONE;
157 } else if (ter->c_cflag & PARODD) {
158 *parity = NTB_PARITY_ODD;
159 } else {
160 *parity = NTB_PARITY_EVEN;
161 }
162 }
163
164 /**
165 * @brief Set the number of stop bits in the termios structure
166 *
167 * @param ter
168 * @param stop_bits
169 *
170 */
native_tty_stop_bits_set(struct termios * ter,enum native_tty_bottom_stop_bits stop_bits)171 static inline void native_tty_stop_bits_set(struct termios *ter,
172 enum native_tty_bottom_stop_bits stop_bits)
173 {
174 switch (stop_bits) {
175 case NTB_STOP_BITS_1:
176 ter->c_cflag &= ~CSTOPB;
177 break;
178 case NTB_STOP_BITS_2:
179 ter->c_cflag |= CSTOPB;
180 break;
181 default:
182 /* Anything else is not supported in termios. */
183 ERROR("Could not set number of data bits.\n");
184 }
185 }
186
187 /**
188 * @brief Get the number of stop bits from the termios structure
189 *
190 * @param ter
191 * @param stop_bits
192 */
native_tty_stop_bits_get(const struct termios * ter,enum native_tty_bottom_stop_bits * stop_bits)193 static inline void native_tty_stop_bits_get(const struct termios *ter,
194 enum native_tty_bottom_stop_bits *stop_bits)
195 {
196 *stop_bits = (ter->c_cflag & CSTOPB) ? NTB_STOP_BITS_2 : NTB_STOP_BITS_1;
197 }
198
199 /**
200 * @brief Set the number of data bits in the termios structure
201 *
202 * @param ter
203 * @param data_bits
204 *
205 */
native_tty_data_bits_set(struct termios * ter,enum native_tty_bottom_data_bits data_bits)206 static inline void native_tty_data_bits_set(struct termios *ter,
207 enum native_tty_bottom_data_bits data_bits)
208 {
209 unsigned int data_bits_to_set = CS5;
210
211 switch (data_bits) {
212 case NTB_DATA_BITS_5:
213 data_bits_to_set = CS5;
214 break;
215 case NTB_DATA_BITS_6:
216 data_bits_to_set = CS6;
217 break;
218 case NTB_DATA_BITS_7:
219 data_bits_to_set = CS7;
220 break;
221 case NTB_DATA_BITS_8:
222 data_bits_to_set = CS8;
223 break;
224 default:
225 /* Anything else is not supported in termios */
226 ERROR("Could not set number of data bits.\n");
227 }
228
229 /* Clear all bits that set the data size */
230 ter->c_cflag &= ~CSIZE;
231 ter->c_cflag |= data_bits_to_set;
232 }
233
234 /**
235 * @brief Get the number of data bits from the termios structure
236 *
237 * @param ter
238 * @param data_bits
239 */
native_tty_data_bits_get(const struct termios * ter,enum native_tty_bottom_data_bits * data_bits)240 static inline void native_tty_data_bits_get(const struct termios *ter,
241 enum native_tty_bottom_data_bits *data_bits)
242 {
243 switch (ter->c_cflag & CSIZE) {
244 case CS5:
245 *data_bits = NTB_DATA_BITS_5;
246 break;
247 case CS6:
248 *data_bits = NTB_DATA_BITS_6;
249 break;
250 case CS7:
251 *data_bits = NTB_DATA_BITS_7;
252 break;
253 case CS8:
254 *data_bits = NTB_DATA_BITS_8;
255 break;
256 default:
257 ERROR("Unsupported data bits setting in termios.\n");
258 }
259 }
260
native_tty_poll_bottom(int fd)261 int native_tty_poll_bottom(int fd)
262 {
263 struct pollfd pfd = { .fd = fd, .events = POLLIN };
264
265 return poll(&pfd, 1, 0);
266 }
267
native_tty_open_tty_bottom(const char * pathname)268 int native_tty_open_tty_bottom(const char *pathname)
269 {
270 int fd = open(pathname, O_RDWR | O_NOCTTY);
271
272 if (fd < 0) {
273 ERROR("Failed to open serial port %s, errno: %i\n", pathname, errno);
274 }
275
276 return fd;
277 }
278
native_tty_configure_bottom(int fd,struct native_tty_bottom_cfg * cfg)279 int native_tty_configure_bottom(int fd, struct native_tty_bottom_cfg *cfg)
280 {
281 int rc, err;
282 /* Structure used to control properties of a serial port */
283 struct termios ter;
284
285 /* Read current terminal driver settings */
286 rc = tcgetattr(fd, &ter);
287 if (rc) {
288 WARN("Could not read terminal driver settings\n");
289 return rc;
290 }
291
292 native_tty_termios_defaults_set(&ter);
293
294 native_tty_baud_speed_set(&ter, cfg->baudrate);
295 native_tty_baud_parity_set(&ter, cfg->parity);
296 native_tty_stop_bits_set(&ter, cfg->stop_bits);
297 native_tty_data_bits_set(&ter, cfg->data_bits);
298
299 cfg->flow_ctrl = NTB_FLOW_CTRL_NONE;
300
301 rc = tcsetattr(fd, TCSANOW, &ter);
302 if (rc) {
303 err = errno;
304 WARN("Could not set serial port settings, reason: %s\n", strerror(err));
305 return err;
306 }
307
308 /* tcsetattr returns success if ANY of the requested changes were successfully carried out,
309 * not if ALL were. So we need to read back the settings and check if they are equal to the
310 * requested ones.
311 */
312 struct termios read_ter;
313
314 rc = tcgetattr(fd, &read_ter);
315 if (rc) {
316 err = errno;
317 WARN("Could not read serial port settings, reason: %s\n", strerror(err));
318 return err;
319 }
320
321 if (ter.c_cflag != read_ter.c_cflag || ter.c_iflag != read_ter.c_iflag ||
322 ter.c_oflag != read_ter.c_oflag || ter.c_lflag != read_ter.c_lflag ||
323 ter.c_line != read_ter.c_line || ter.c_ispeed != read_ter.c_ispeed ||
324 ter.c_ospeed != read_ter.c_ospeed || 0 != memcmp(ter.c_cc, read_ter.c_cc, NCCS)) {
325 WARN("Read serial port settings do not match set ones.\n");
326 return -1;
327 }
328
329 /* Flush both input and output */
330 rc = tcflush(fd, TCIOFLUSH);
331 if (rc) {
332 WARN("Could not flush serial port\n");
333 return rc;
334 }
335
336 return 0;
337 }
338
native_tty_read_bottom_cfg(int fd,struct native_tty_bottom_cfg * cfg)339 int native_tty_read_bottom_cfg(int fd, struct native_tty_bottom_cfg *cfg)
340 {
341 struct termios ter;
342 int rc = 0;
343
344 rc = tcgetattr(fd, &ter);
345 if (rc != 0) {
346 int err = 0;
347
348 err = errno;
349 WARN("Could not read terminal driver settings: %s\n", strerror(err));
350 return -nsi_errno_to_mid(err);
351 }
352
353 native_tty_baud_speed_get(&ter, &cfg->baudrate);
354 native_tty_baud_parity_get(&ter, &cfg->parity);
355 native_tty_data_bits_get(&ter, &cfg->data_bits);
356 native_tty_stop_bits_get(&ter, &cfg->stop_bits);
357 cfg->flow_ctrl = NTB_FLOW_CTRL_NONE;
358
359 return 0;
360 }
361