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