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