From b79b5d652b7088c76535b3740ac421f7657e1255 Mon Sep 17 00:00:00 2001 From: Gwenhael Goavec-Merou Date: Sun, 27 Nov 2022 09:26:20 +0100 Subject: [PATCH] src/uart_ll: lowlevel serial driver --- CMakeLists.txt | 5 +- src/uart_ll.cpp | 341 ++++++++++++++++++++++++++++++++++++++++++++++++ src/uart_ll.hpp | 93 +++++++++++++ 3 files changed, 438 insertions(+), 1 deletion(-) create mode 100644 src/uart_ll.cpp create mode 100644 src/uart_ll.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 93f1a0b..32b0bda 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -203,9 +203,12 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Windows") list(APPEND OPENFPGALOADER_HEADERS src/pathHelper.hpp) endif() -# libusb_attach_kernel_driver is only available on Linux. if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + # libusb_attach_kernel_driver is only available on Linux. add_definitions(-DATTACH_KERNEL) + # uart_ll only tested on Linux + target_sources(openFPGALoader PRIVATE src/uart_ll.cpp) + list (APPEND OPENFPGALOADER_HEADERS src/uart_ll.hpp) endif() if (ENABLE_UDEV) diff --git a/src/uart_ll.cpp b/src/uart_ll.cpp new file mode 100644 index 0000000..d79684b --- /dev/null +++ b/src/uart_ll.cpp @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2022 Gwenhael Goavec-Merou + */ + +#include "uart_ll.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "display.hpp" + +Uart_ll::Uart_ll(const std::string &filename, uint32_t clkHz, + uint8_t byteSize, bool twoStopBits): _clkHz(clkHz), + _byteSize(CS8), _stopBits(twoStopBits) +{ + switch (byteSize) { + case 5: + _byteSize = CS5; + break; + case 6: + _byteSize = CS6; + break; + case 7: + _byteSize = CS7; + break; + case 8: + _byteSize = CS8; + break; + default: + std::runtime_error("Error: byteSize must be between 5 and 8"); + } + + /* open device */ + _serial = open(filename.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK); + if (_serial == -1) + throw std::runtime_error("Error: failed to open " + filename + + " (" + strerror(errno) + ")"); + + /* configure port */ + if (port_configure() < 0) + throw std::runtime_error("Error: port configuration failed"); + + // set baudrate + if (setClkFreq(_clkHz) < -1) + throw std::runtime_error("Error: baudrate configuration failed"); +} + +Uart_ll::~Uart_ll() { + int err; + /* reapply original configuration */ + if ((err = tcsetattr(_serial, TCSANOW, &_prev_termios)) != 0) { + printError("error %d from tcsetattr: " + std::to_string(err)); + } + + /* close device */ + close(_serial); +} + +int Uart_ll::port_configure(void) +{ + int err; + + /* store current configuration */ + if ((err = tcgetattr(_serial, &_prev_termios)) != 0) { + printError("error to retrieve current configuration: " + + std::to_string(err) + " " + strerror(errno)); + return -1; + } + + /* copy termios structure configuration */ + memcpy(&_curr_termios, &_prev_termios, sizeof(_prev_termios)); + + /* controls flags */ + /* clear default configuration */ + _curr_termios.c_cflag &= ~(CSIZE | CSTOPB | CRTSCTS); + /* set byte size (5,6,7,8) + * enable receiver + * disable modem control lines + */ + _curr_termios.c_cflag |= (_byteSize | CREAD | CLOCAL); + /* 2 stop bits ? */ + if (_stopBits) + _curr_termios.c_cflag |= CSTOPB; + + /* input mode */ + _curr_termios.c_iflag &= ~IGNBRK; // disable break processing + _curr_termios.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl + + /* output mode */ + _curr_termios.c_oflag = 0; // no remapping, no delays + + // no canonical processing + _curr_termios.c_lflag = 0; // no signaling chars, no echo, + + _curr_termios.c_cc[VMIN] = 0; // read doesn't block + _curr_termios.c_cc[VTIME] = 5; // 0.5 seconds read timeout + + if ((err = tcsetattr(_serial, TCSANOW, &_curr_termios)) != 0) { + printError("error %d from tcsetattr: " + std::to_string(err) + + " " + strerror(errno)); + return -1; + } + + return 0; +} + +int Uart_ll::setClkFreq(uint32_t clkHz) +{ + int err; + speed_t baud = freq_to_baud(clkHz); + + /* set input baudrate */ + cfsetispeed(&_curr_termios, baud); + /* set output baudrate */ + cfsetospeed(&_curr_termios, baud); + /* apply modifications */ + if ((err = tcsetattr(_serial, TCSANOW, &_curr_termios)) != 0) { + printError("Error during baudrate configuration: tcsetattr == " + + std::to_string(err)); + return -1; + } + _clkHz = clkHz; + _baudrate = baud; + return clkHz; +} + +uint32_t Uart_ll::getClkFreq() +{ + return baud_to_freq(cfgetispeed(&_curr_termios)); +} + +int Uart_ll::write(const unsigned char *data, int size) +{ + const char *ptr = reinterpret_cast(data); + ssize_t len = size; + /* set a select to block for serial data or timeout */ + fd_set fd_write; + timespec timeout_ts; + timeout_ts.tv_sec = 5; + timeout_ts.tv_nsec = 0; + + do { + ssize_t s = ::write(_serial, ptr, len); + if (s < 0) { + if (errno != 11) { + printError("Error: Failed to write: " + std::to_string(errno) + + " " + strerror(errno)); + return s; + } + } else if (s == len) { + break; + } + + ptr += s; + len -= s; + + FD_ZERO(&fd_write); + FD_SET(_serial, &fd_write); + int r = pselect(_serial + 1, NULL, &fd_write, NULL, &timeout_ts, NULL); + + if (r < 0) { + int t = errno; + printf("uart_ll errror: write failed with error %d (%d: %s)\n", r, + errno, strerror(errno)); + // interrupt ? + return (t == EINTR) ? -1 : -2; + } + + /* timeout */ + if (r == 0) + return -3; + + if (!FD_ISSET(_serial, &fd_write)) { + printError( + "uart_ll error: something to write but from file descriptor\n"); + return -4; + } + } while (len > 0); + + return size; +} + +int Uart_ll::read(std::string *buf, int maxsize) +{ + int size = maxsize; + int read_size = 0; + char c; + + do { + ssize_t ret = ::read(_serial, &c, 1); + if (ret < 0) { + char mess[256]; + snprintf(mess, 256, "Error: Failed to read: %d %s", + errno, strerror(errno)); + printError(mess); + return ret; + } + buf->append(1, c); + read_size++; + size--; + } while (size > 0); + + return read_size; +} + +bool Uart_ll::flush(int maxsize) +{ + int size = maxsize; + int read_size = 0; + char c; + int timeout = 1000; + std::string buf; + + do { + ssize_t ret = ::read(_serial, &c, 1); + if (ret < 0) { + if (errno == 11) + return true; + char mess[256]; + snprintf(mess, 256, "Error: Failed to read: %d %s", + errno, strerror(errno)); + printError(mess); + return ret; + } else if (ret == 0) { + timeout--; + } else { + buf.append(1, c); + read_size++; + size--; + } + } while (size > 0 && timeout > 0); + + return true; +} +int Uart_ll::read_until(std::string *buf, uint8_t end) +{ + int read_size = 0; + char c[256]; + bool is_end = false; + /* set a select to block for serial data or timeout */ + fd_set fd_read; + timespec timeout_ts; + timeout_ts.tv_sec = 5; + timeout_ts.tv_nsec = 0; + do { + FD_ZERO(&fd_read); + FD_SET(_serial, &fd_read); + int r = pselect(_serial + 1, &fd_read, NULL, NULL, &timeout_ts, NULL); + + if (r < 0) { + int t = errno; + printf("uart_ll errror: read failed with error %d (%d: %s)\n", r, + errno, strerror(errno)); + if (t == EINTR) // interrupt + return -1; + return -2; + } + + /* timeout */ + if (r == 0) + return -3; + + if (!FD_ISSET(_serial, &fd_read)) { + printError( + "uart_ll error: something to read but from file descriptor\n"); + return -4; + } + + int ret = ::read(_serial, c, 256); + if (ret < 0) { + if (errno == 11) { // temporarily unavailable + printError("uart_ll error: device unavailable\n"); + return -5; + } + } + + for (int i = 0; i < ret; i++) { + if (c[i] == '\r' && end == '\n') + continue; + if (c[i] == end) { + is_end = true; + break; + } + buf->append(1, c[i]); + read_size++; + } + } while (!is_end); + + return read_size; +} + +speed_t Uart_ll::freq_to_baud(uint32_t clkHz) +{ + if (clkHz > 115200) + return B230400; + else if (clkHz > 57600) + return B115200; + else if (clkHz > 38400) + return B57600; + else if (clkHz > 19200) + return B38400; + else if (clkHz > 9600) + return B19200; + else if (clkHz > 4800) + return B9600; + else if (clkHz > 2400) + return B4800; + else // no exhaustive list + return B2400; +} + +uint32_t Uart_ll::baud_to_freq(speed_t baud) +{ + switch(baud) { + case B230400: + return 230400; + case B115200: + return 115200; + case B57600: + return 57600; + case B38400: + return 38400; + case B19200: + return 19200; + case B9600: + return 9600; + case B4800: + return 4800; + default: // no exhaustive list + return 2400; + } +} diff --git a/src/uart_ll.hpp b/src/uart_ll.hpp new file mode 100644 index 0000000..19d0fee --- /dev/null +++ b/src/uart_ll.hpp @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: Apache-2.0 +/* + * Copyright (C) 2022 Gwenhael Goavec-Merou + */ + +#ifndef SRC_UART_LL_HPP_ +#define SRC_UART_LL_HPP_ + +#include +#include + +#include + +/*! + * \file uart_ll + * \class Uart_ll + * \brief low level implementation for UART protocol + * \author Gwenhael Goavec-Merou + */ +class Uart_ll { + public: + /*! + * \brief constructor + * \param[in] filename: /dev/ttyxx path + * \param[in] clkHz: baudrate (in Hz) + * \param[in] byteSize: transaction size (5 to 8) + * \param[in] stopBits: 1 or 2 stop bit + * \param[in] firmware_path: firmware to load + */ + + Uart_ll(const std::string &filename, uint32_t clkHz, + uint8_t byteSize, bool stopBits); + ~Uart_ll(); + + int setClkFreq(uint32_t clkHz); + uint32_t getClkFreq(); + + /*! + * \brief send buffer content + * \param[in] data: buffer + * \param[in] size: number of char to send + * \return size if ok, < 0 otherwise + */ + int write(const unsigned char *data, int size); + /*! + * \brief read maxsize char from device + * \param[in] buf: buffer to fill + * \param[in] maxsize: number of char to read + * \return read size if ok, < 0 otherwise + */ + int read(std::string *buf, int maxsize); + /*! + * \brief read char from device until receiving end char + * \param[in] buf: buffer to fill + * \param[in] end: number of char to read + * \return read size if ok, < 0 otherwise + */ + int read_until(std::string *buf, uint8_t end = '\n'); + + /*! + * \brief flush by reading maxsize char from device + * \param[in] maxsize: number of char to read + * \return true if ok, false otherwise + */ + bool flush(int maxsize=64); + private: + /*! + * \brief serial port configuration + * \return -1 if something fails, 0 otherwise + */ + int port_configure(void); + /*! + * \brief convert a frequency to the baudrate (BXXX) + * \param[in] clkHz: clock frequency + * \return the corresponding baudrate value + */ + speed_t freq_to_baud(uint32_t clkHz); + /*! + * \brief convert the baudrate (BXXX) to the corresponding frequency + * \param[in] baud: a speed_t (BXXX) value + * \return the corresponding frequency (Hz) + */ + uint32_t baud_to_freq(speed_t baud); + + struct termios _prev_termios; /*! original serial port configuration */ + struct termios _curr_termios; /*! current serial port configuration */ + speed_t _baudrate; /*! current baudrate */ + uint32_t _clkHz; /*! current clk frequency */ + int _serial; /*! device file descriptor */ + uint32_t _byteSize; /*! transfer size */ + bool _stopBits; +}; +#endif // SRC_UART_LL_HPP_