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_tracing.h>
19 
20 #define WARN(...)  nsi_print_warning(__VA_ARGS__)
21 #define ERROR(...) nsi_print_error_and_exit(__VA_ARGS__)
22 
23 #define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
24 
25 struct baudrate_termios_pair {
26 	int baudrate;
27 	speed_t termios_baudrate;
28 };
29 
30 /**
31  * @brief Lookup table for mapping the baud rate to the macro understood by termios.
32  */
33 static const struct baudrate_termios_pair baudrate_lut[] = {
34 	{1200, B1200},       {1800, B1800},       {2400, B2400},       {4800, B4800},
35 	{9600, B9600},       {19200, B19200},     {38400, B38400},     {57600, B57600},
36 	{115200, B115200},   {230400, B230400},   {460800, B460800},   {500000, B500000},
37 	{576000, B576000},   {921600, B921600},   {1000000, B1000000}, {1152000, B1152000},
38 	{1500000, B1500000}, {2000000, B2000000}, {2500000, B2500000}, {3000000, B3000000},
39 	{3500000, B3500000}, {4000000, B4000000},
40 };
41 
42 /**
43  * @brief Set given termios to defaults appropriate for communicating with serial port devices.
44  *
45  * @param ter
46  */
native_tty_termios_defaults_set(struct termios * ter)47 static inline void native_tty_termios_defaults_set(struct termios *ter)
48 {
49 	/* Set terminal in "serial" mode:
50 	 *  - Not canonical (no line input)
51 	 *  - No signal generation from Ctr+{C|Z..}
52 	 *  - No echoing
53 	 */
54 	ter->c_lflag &= ~(ICANON | ISIG | ECHO);
55 
56 	/* No special interpretation of output bytes.
57 	 * No conversion of newline to carriage return/line feed.
58 	 */
59 	ter->c_oflag &= ~(OPOST | ONLCR);
60 
61 	/* No software flow control. */
62 	ter->c_iflag &= ~(IXON | IXOFF | IXANY);
63 
64 	/* No blocking, return immediately with what is available. */
65 	ter->c_cc[VMIN] = 0;
66 	ter->c_cc[VTIME] = 0;
67 
68 	/* No special handling of bytes on receive. */
69 	ter->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL);
70 
71 	/* - Enable reading data and ignore control lines */
72 	ter->c_cflag |= CREAD | CLOCAL;
73 }
74 
75 /**
76  * @brief Set the baud rate speed in the termios structure
77  *
78  * @param ter
79  * @param baudrate
80  */
native_tty_baud_speed_set(struct termios * ter,int baudrate)81 static inline void native_tty_baud_speed_set(struct termios *ter, int baudrate)
82 {
83 	for (int i = 0; i < ARRAY_SIZE(baudrate_lut); i++) {
84 		if (baudrate_lut[i].baudrate == baudrate) {
85 			cfsetospeed(ter, baudrate_lut[i].termios_baudrate);
86 			cfsetispeed(ter, baudrate_lut[i].termios_baudrate);
87 			return;
88 		}
89 	}
90 	ERROR("Could not set baudrate, as %d is not supported.\n", baudrate);
91 }
92 
93 /**
94  * @brief Set parity setting in the termios structure
95  *
96  * @param ter
97  * @param parity
98  */
native_tty_baud_parity_set(struct termios * ter,enum native_tty_bottom_parity parity)99 static inline void native_tty_baud_parity_set(struct termios *ter,
100 					      enum native_tty_bottom_parity parity)
101 {
102 	switch (parity) {
103 	case NTB_PARITY_NONE:
104 		ter->c_cflag &= ~PARENB;
105 		break;
106 	case NTB_PARITY_ODD:
107 		ter->c_cflag |= PARENB;
108 		ter->c_cflag |= PARODD;
109 		break;
110 	case NTB_PARITY_EVEN:
111 		ter->c_cflag |= PARENB;
112 		ter->c_cflag &= ~PARODD;
113 		break;
114 	default:
115 		/* Parity options mark and space are not supported on this driver. */
116 		ERROR("Could not set parity.\n");
117 	}
118 }
119 
120 /**
121  * @brief Set the number of stop bits in the termios structure
122  *
123  * @param ter
124  * @param stop_bits
125  *
126  */
native_tty_stop_bits_set(struct termios * ter,enum native_tty_bottom_stop_bits stop_bits)127 static inline void native_tty_stop_bits_set(struct termios *ter,
128 					    enum native_tty_bottom_stop_bits stop_bits)
129 {
130 	switch (stop_bits) {
131 	case NTB_STOP_BITS_1:
132 		ter->c_cflag &= ~CSTOPB;
133 		break;
134 	case NTB_STOP_BITS_2:
135 		ter->c_cflag |= CSTOPB;
136 		break;
137 	default:
138 		/* Anything else is not supported in termios. */
139 		ERROR("Could not set number of data bits.\n");
140 	}
141 }
142 
143 /**
144  * @brief Set the number of data bits in the termios structure
145  *
146  * @param ter
147  * @param data_bits
148  *
149  */
native_tty_data_bits_set(struct termios * ter,enum native_tty_bottom_data_bits data_bits)150 static inline void native_tty_data_bits_set(struct termios *ter,
151 					    enum native_tty_bottom_data_bits data_bits)
152 {
153 	unsigned int data_bits_to_set = CS5;
154 
155 	switch (data_bits) {
156 	case NTB_DATA_BITS_5:
157 		data_bits_to_set = CS5;
158 		break;
159 	case NTB_DATA_BITS_6:
160 		data_bits_to_set = CS6;
161 		break;
162 	case NTB_DATA_BITS_7:
163 		data_bits_to_set = CS7;
164 		break;
165 	case NTB_DATA_BITS_8:
166 		data_bits_to_set = CS8;
167 		break;
168 	default:
169 		/* Anything else is not supported in termios */
170 		ERROR("Could not set number of data bits.\n");
171 	}
172 
173 	/* Clear all bits that set the data size */
174 	ter->c_cflag &= ~CSIZE;
175 	ter->c_cflag |= data_bits_to_set;
176 }
177 
native_tty_poll_bottom(int fd)178 int native_tty_poll_bottom(int fd)
179 {
180 	struct pollfd pfd = { .fd = fd, .events = POLLIN };
181 
182 	return poll(&pfd, 1, 0);
183 }
184 
native_tty_open_tty_bottom(const char * pathname)185 int native_tty_open_tty_bottom(const char *pathname)
186 {
187 	int fd = open(pathname, O_RDWR | O_NOCTTY);
188 
189 	if (fd < 0) {
190 		ERROR("Failed to open serial port %s, errno: %i\n", pathname, errno);
191 	}
192 
193 	return fd;
194 }
195 
native_tty_configure_bottom(int fd,struct native_tty_bottom_cfg * cfg)196 int native_tty_configure_bottom(int fd, struct native_tty_bottom_cfg *cfg)
197 {
198 	int rc, err;
199 	/* Structure used to control properties of a serial port */
200 	struct termios ter;
201 
202 	/* Read current terminal driver settings */
203 	rc = tcgetattr(fd, &ter);
204 	if (rc) {
205 		WARN("Could not read terminal driver settings\n");
206 		return rc;
207 	}
208 
209 	native_tty_termios_defaults_set(&ter);
210 
211 	native_tty_baud_speed_set(&ter, cfg->baudrate);
212 	native_tty_baud_parity_set(&ter, cfg->parity);
213 	native_tty_stop_bits_set(&ter, cfg->stop_bits);
214 	native_tty_data_bits_set(&ter, cfg->data_bits);
215 
216 	cfg->flow_ctrl = NTB_FLOW_CTRL_NONE;
217 
218 	rc = tcsetattr(fd, TCSANOW, &ter);
219 	if (rc) {
220 		err = errno;
221 		WARN("Could not set serial port settings, reason: %s\n", strerror(err));
222 		return err;
223 	}
224 
225 	/* tcsetattr returns success if ANY of the requested changes were successfully carried out,
226 	 * not if ALL were. So we need to read back the settings and check if they are equal to the
227 	 * requested ones.
228 	 */
229 	struct termios read_ter;
230 
231 	rc = tcgetattr(fd, &read_ter);
232 	if (rc) {
233 		err = errno;
234 		WARN("Could not read serial port settings, reason: %s\n", strerror(err));
235 		return err;
236 	}
237 
238 	if (ter.c_cflag != read_ter.c_cflag || ter.c_iflag != read_ter.c_iflag ||
239 	    ter.c_oflag != read_ter.c_oflag || ter.c_lflag != read_ter.c_lflag ||
240 	    ter.c_line != read_ter.c_line || ter.c_ispeed != read_ter.c_ispeed ||
241 	    ter.c_ospeed != read_ter.c_ospeed || 0 != memcmp(ter.c_cc, read_ter.c_cc, NCCS)) {
242 		WARN("Read serial port settings do not match set ones.\n");
243 		return -1;
244 	}
245 
246 	/* Flush both input and output */
247 	rc = tcflush(fd, TCIOFLUSH);
248 	if (rc) {
249 		WARN("Could not flush serial port\n");
250 		return rc;
251 	}
252 
253 	return 0;
254 }
255