src/uart_ll: lowlevel serial driver

This commit is contained in:
Gwenhael Goavec-Merou 2022-11-27 09:26:20 +01:00
parent a0180f0bc6
commit b79b5d652b
3 changed files with 438 additions and 1 deletions

View File

@ -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)

341
src/uart_ll.cpp Normal file
View File

@ -0,0 +1,341 @@
// SPDX-License-Identifier: Apache-2.0
/*
* Copyright (C) 2022 Gwenhael Goavec-Merou <gwenhael.goavec-merou@trabucayre.com>
*/
#include "uart_ll.hpp"
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <termios.h>
#include <unistd.h>
#include <stdexcept>
#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<const char *>(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;
}
}

93
src/uart_ll.hpp Normal file
View File

@ -0,0 +1,93 @@
// SPDX-License-Identifier: Apache-2.0
/*
* Copyright (C) 2022 Gwenhael Goavec-Merou <gwenhael.goavec-merou@trabucayre.com>
*/
#ifndef SRC_UART_LL_HPP_
#define SRC_UART_LL_HPP_
#include <stdint.h>
#include <termios.h>
#include <string>
/*!
* \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_