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