/** * @brief "Bottom" of native tty uart driver * * Copyright (c) 2023 Marko Sagadin * SPDX-License-Identifier: Apache-2.0 */ #include "uart_native_tty_bottom.h" #include #include #include #include #include #include #include #define WARN(...) nsi_print_warning(__VA_ARGS__) #define ERROR(...) nsi_print_error_and_exit(__VA_ARGS__) #define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) struct baudrate_termios_pair { int baudrate; speed_t termios_baudrate; }; /** * @brief Lookup table for mapping the baud rate to the macro understood by termios. */ static const struct baudrate_termios_pair baudrate_lut[] = { {1200, B1200}, {1800, B1800}, {2400, B2400}, {4800, B4800}, {9600, B9600}, {19200, B19200}, {38400, B38400}, {57600, B57600}, {115200, B115200}, {230400, B230400}, {460800, B460800}, {500000, B500000}, {576000, B576000}, {921600, B921600}, {1000000, B1000000}, {1152000, B1152000}, {1500000, B1500000}, {2000000, B2000000}, {2500000, B2500000}, {3000000, B3000000}, {3500000, B3500000}, {4000000, B4000000}, }; /** * @brief Set given termios to defaults appropriate for communicating with serial port devices. * * @param ter */ static inline void native_tty_termios_defaults_set(struct termios *ter) { /* Set terminal in "serial" mode: * - Not canonical (no line input) * - No signal generation from Ctr+{C|Z..} * - No echoing */ ter->c_lflag &= ~(ICANON | ISIG | ECHO); /* No special interpretation of output bytes. * No conversion of newline to carriage return/line feed. */ ter->c_oflag &= ~(OPOST | ONLCR); /* No software flow control. */ ter->c_iflag &= ~(IXON | IXOFF | IXANY); /* No blocking, return immediately with what is available. */ ter->c_cc[VMIN] = 0; ter->c_cc[VTIME] = 0; /* No special handling of bytes on receive. */ ter->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL); /* - Enable reading data and ignore control lines */ ter->c_cflag |= CREAD | CLOCAL; } /** * @brief Set the baud rate speed in the termios structure * * @param ter * @param baudrate */ static inline void native_tty_baud_speed_set(struct termios *ter, int baudrate) { for (int i = 0; i < ARRAY_SIZE(baudrate_lut); i++) { if (baudrate_lut[i].baudrate == baudrate) { cfsetospeed(ter, baudrate_lut[i].termios_baudrate); cfsetispeed(ter, baudrate_lut[i].termios_baudrate); return; } } ERROR("Could not set baudrate, as %d is not supported.\n", baudrate); } /** * @brief Set parity setting in the termios structure * * @param ter * @param parity */ static inline void native_tty_baud_parity_set(struct termios *ter, enum native_tty_bottom_parity parity) { switch (parity) { case NTB_PARITY_NONE: ter->c_cflag &= ~PARENB; break; case NTB_PARITY_ODD: ter->c_cflag |= PARENB; ter->c_cflag |= PARODD; break; case NTB_PARITY_EVEN: ter->c_cflag |= PARENB; ter->c_cflag &= ~PARODD; break; default: /* Parity options mark and space are not supported on this driver. */ ERROR("Could not set parity.\n"); } } /** * @brief Set the number of stop bits in the termios structure * * @param ter * @param stop_bits * */ static inline void native_tty_stop_bits_set(struct termios *ter, enum native_tty_bottom_stop_bits stop_bits) { switch (stop_bits) { case NTB_STOP_BITS_1: ter->c_cflag &= ~CSTOPB; break; case NTB_STOP_BITS_2: ter->c_cflag |= CSTOPB; break; default: /* Anything else is not supported in termios. */ ERROR("Could not set number of data bits.\n"); } } /** * @brief Set the number of data bits in the termios structure * * @param ter * @param stop_bits * */ static inline void native_tty_data_bits_set(struct termios *ter, enum native_tty_bottom_data_bits data_bits) { unsigned int data_bits_to_set = CS5; switch (data_bits) { case NTB_DATA_BITS_5: data_bits_to_set = CS5; break; case NTB_DATA_BITS_6: data_bits_to_set = CS6; break; case NTB_DATA_BITS_7: data_bits_to_set = CS7; break; case NTB_DATA_BITS_8: data_bits_to_set = CS8; break; default: /* Anything else is not supported in termios */ ERROR("Could not set number of data bits.\n"); } /* Clear all bits that set the data size */ ter->c_cflag &= ~CSIZE; ter->c_cflag |= data_bits_to_set; } int native_tty_open_tty_bottom(const char *pathname) { int fd = open(pathname, O_RDWR | O_NOCTTY); if (fd < 0) { ERROR("Failed to open serial port %s, errno: %i\n", pathname, errno); } return fd; } int native_tty_configure_bottom(int fd, struct native_tty_bottom_cfg *cfg) { int rc, err; /* Structure used to control properties of a serial port */ struct termios ter; /* Read current terminal driver settings */ rc = tcgetattr(fd, &ter); if (rc) { WARN("Could not read terminal driver settings\n"); return rc; } native_tty_termios_defaults_set(&ter); native_tty_baud_speed_set(&ter, cfg->baudrate); native_tty_baud_parity_set(&ter, cfg->parity); native_tty_stop_bits_set(&ter, cfg->stop_bits); native_tty_data_bits_set(&ter, cfg->data_bits); cfg->flow_ctrl = NTB_FLOW_CTRL_NONE; rc = tcsetattr(fd, TCSANOW, &ter); if (rc) { err = errno; WARN("Could not set serial port settings, reason: %s\n", strerror(err)); return err; } /* tcsetattr returns success if ANY of the requested changes were successfully carried out, * not if ALL were. So we need to read back the settings and check if they are equal to the * requested ones. */ struct termios read_ter; rc = tcgetattr(fd, &read_ter); if (rc) { err = errno; WARN("Could not read serial port settings, reason: %s\n", strerror(err)); return err; } if (ter.c_cflag != read_ter.c_cflag || ter.c_iflag != read_ter.c_iflag || ter.c_oflag != read_ter.c_oflag || ter.c_lflag != read_ter.c_lflag || ter.c_line != read_ter.c_line || ter.c_ispeed != read_ter.c_ispeed || ter.c_ospeed != read_ter.c_ospeed || 0 != memcmp(ter.c_cc, read_ter.c_cc, NCCS)) { WARN("Read serial port settings do not match set ones.\n"); return -1; } /* Flush both input and output */ rc = tcflush(fd, TCIOFLUSH); if (rc) { WARN("Could not flush serial port\n"); return rc; } return 0; }