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