Unable to Read From Serial Device After Unplugging and Replugging Connector - c

I'm have a Linux application that is supposed to read from serial device /dev/ttyS0. The serial device is opened in the following manner:
// Open the serial port
if((serial_device = open("/dev/ttyS0", O_RDWR | O_NOCTTY)) < 0){
fprintf(stderr, "ERROR: Open\n");
exit(EXIT_FAILURE);
}
// Get serial device attributes
if(tcgetattr(serial_device,&options)){
fprintf(stderr, "ERROR: Terminal Get Attributes\n");
exit(EXIT_FAILURE);
}
cfsetspeed(&options,speed); // Set I/O baud rates
cfmakeraw(&options); // Set options to transceive raw data
options.c_cflag |= (CLOCAL | CREAD); // Enable the receiver and set local mode
options.c_cflag &= ~CSTOPB; // 1 stop bit
options.c_cflag &= ~CRTSCTS; // Disable hardware flow control
options.c_cc[VMIN] = 1; // Minimum number of characters to read
options.c_cc[VTIME] = 10; // One second timeout
// Set the new serial device attributes
if(tcsetattr(serial_device, TCSANOW, &options)){
fprintf(stderr, "ERROR: Terminal Set Attributes\n");
exit(EXIT_FAILURE);
}
I then use the select function to try and read from the serial device:
// Flush I/O Bffer
if(tcflush(serial_device,TCIOFLUSH)){
fprintf(stderr, "ERROR: I/O Flush\n");
exit(EXIT_FAILURE);
}
// Write message to board
if(write(serial_device,msg, strlen(msg)) != (int)strlen(msg)){
fprintf(stderr, "ERROR: Write\n");
exit(EXIT_FAILURE);
}
switch(select(serial_device+1, &set, NULL, NULL, &timeout)){
// Error
case -1:
fprintf(stderr, "ERROR: Select\n");
exit(EXIT_FAILURE);
// Timeout
case 0:
success = false;
break;
// Input ready
default:
// Try to read a character
switch(read(serial_device, &c, 1)){
// Error (miss)
case -1:
success = false;
break;
// Got a character
default:
msg[i++] = c;
break;
}
break;
}
// Set 200ms timeout
this->timeout.tv_sec = 0;
this->timeout.tv_usec = 200000;
}
I've tried reopening the port by determining if the read was not successful:
if(!success)
close(serial_device);
openPort(); // Same as above
}
However, the act of physically unplugging the serial connector will result in the application being unable to read anything further, and select will do nothing but time out. Plugging the connector back in while the application is running will not fix the issue, and select will continue to detect nothing.
The only way to successfully read from the serial port again is to restart the application. I'm wondering why this is, and how I can recover from the serial connector being unplugged at runtime.

The use of select() with just one file descriptor is unusual. It also adds a level of complexity.
Since the serial port is configured for non-canonical input, with proper selection of VMIN and VTIME, you might be able to accomplish the read of a char at a time with simpler code. E.G. try VMIN = 1 and VTIME = 10*timeout.tv_sec
However as you figured out, and if you are willing to handle (or want) a timeout rather than wait for at least one character to arrive, then VMIN = 0 will emulate your original code with the select().
VMIN = 0 and VTIME > 0
This is a pure timed read. If data are available in the input queue, it's transferred to the caller's buffer up to a maximum of nbytes, and returned immediately to the caller. Otherwise the driver blocks until data arrives, or when VTIME tenths expire from the start of the call. If the timer expires without data, zero is returned. A single byte is sufficient to satisfy this read call, but if more is available in the input queue, it's returned to the caller. Note that this is an overall timer, not an intercharacter one.
However (like the OP) I'm baffled as to why reconnecting the port connector should disrupt any reads or select monitoring, and have never encountered such an issue.

Related

POSIX read() call remains blocked forever regardless of what's set in VTIME

I need to configure the UART settings such that the read() call remains blocked until a certain time before it's unblocked again if it didn't receive any data within the timeout. So if the timeout is 5 seconds, it remains blocked till 5 seconds max if it doesn't receive any byte and then unblocks...
I tried using VMIN which should block the read() call until no character is read within the time allowed, after which the call to read() returns 0, but that doesn't seem to be the case for me: the read() call remains blocked forever and as soon as I enter stuff in a minicom serial session, read() unblocks and then goes back to getting blocked again.
I'm not sure if it's taking into account VTIME setting, or maybe I'm misconfiguring it.
Rather, would select() with a timeout be a better approach?
#define SERIAL_PORT "/dev/ttyUSB4"
pthread_t td;
int fd;
int SerialOpen()
{
struct termios term;
fd = open(SERIAL_PORT, O_RDWR);
if (fd < 0)
{
perror ("failed to open");
return -1;
}
bzero(&term, sizeof(term));
cfmakeraw(&term);
term.c_cflag |= CREAD;
tcgetattr(fd, &term);
term.c_iflag &= ~ICRNL;
term.c_iflag &= ~INLCR;
term.c_iflag &= ~IGNBRK;
term.c_oflag &= ~OCRNL;
term.c_oflag &= ~ONLCR;
term.c_oflag &= ~OPOST;
term.c_lflag &= ~ICANON;
term.c_lflag &= ~ISIG;
term.c_lflag &= ~IEXTEN;
term.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|ECHOCTL|ECHOPRT|ECHOKE);
cfsetspeed(term, B115200); // set baud rate
term.c_cflag &= ~CSTOPB;
term.c_cflag |= CS8;
// disable flow control
term.c_cflag &= ~CRTSCTS;
term.c_iflag &= ~(IXON | IXOFF);
term.c_cflag &= ~PARENB; // no parity
term.c_cc[VTIME] = 50; // Wait for up to 5s (50 deciseconds), returning as soon as any data is received.
term.c_cc[VMIN] = 0;
if ( (tcsetattr(fd, TCSANOW, &term)) < 0)
{
perror ("Failed to set attr");
return -1;
}
return 1;
}
void *Rx(void *arg)
{
char buff[100] = {0};
while(1)
{
int sz = read(fd, buff, sizeof(buff)); // block until VTIME times out
if (sz < 0)
{
perror ("Read failed");
}
printf ("Received bytes %d: %s\n", sz, buff);
}
}
int main()
{
int ret = SerialOpen();
if (ret < 0)
{
return -1;
}
if (pthread_create(&td, NULL, Rx, NULL) != 0)
{
printf("Fail to create thread!\n");
}
pthread_join(td, 0);
return 0;
}
Caveat: Prefaced by the top comments.
Be sure you're applying the fixes I suggested in the comments above.
select may have the same issues as VMIN/VTIME. select operates at a level after the TTY layer [which is where VMIN/VTIME operate]. At a lower level is the USB driver. If the TTY layer isn't set up correctly, select may have the same issues as read (i.e. probably not the issue). Although, you might do: open(/dev/whatever,O_RDWR | O_NONBLOCK)
But, I suspect that the issue is at a more fundamental/lower level. I think the USB layer/level is part of the problem.
USB-to-RS232 cables are tricky. The cable may need active H/W flow control enabled to operate. So, just disabling H/W flow control via (e.g.) clearing CRTSCTS may not work too well.
Double check: Are you sure that you are getting all the way through the configuration/initialization of the device. Could the open be hanging? You could add debug printf statements throughout the code [easier than using gdb for this].
What is the specific manufacturer and device for the USB cable? What is the specific manufacturer/model of the end/UART device? Although the standard/generic USB serial driver (e.g. usbserial) should work, some vendors need a specific driver (e.g. FTDI has its own USB level driver). So, you may need to consult the manufacturer's website/datasheet for the specific devices in question.
Also, sometimes a USB cable shows up on two different /dev/tty* devices. You may have the wrong one. You may need ttyS* instead of ttyUSB* or the device can show up as (e.g.) ttyACM* !!! This can occur even if ttyS* or ttyUSB* are [also] present.
Look at:
ls -l /dev
/proc/devices
/sys/devices
lsmod
lsusb
lspci
dmesg [and, maybe, syslog output]
Did you [or the system] modprobe usbserial?
Specifically, the dmesg output should say which tty* the device is connected to.
Some resources (from a "all the words" websearch on linux /dev USB uart cable):
https://www.cyberciti.biz/faq/find-out-linux-serial-ports-with-setserial/
https://unix.stackexchange.com/questions/81754/how-to-match-a-ttyusbx-device-to-a-usb-serial-device

How to change the baudrate of a serial port at runtime using C?

I have written some C code for aarch64-based SoC (Rockchip RK3399) with Debian 9 LXDE, to receive data from a GPS module. The GPS module is connected to "ttyS4" port in my SoC. I have created a pthread to receive data from the GPS module. I'm using the termios library. So my flow goes as follows:
Initialize the UART port (baud rate, parity, stop bits etc.).
Create a thread to receive the data from the module.
Now I need to change the baud rate of the UART upon receiving the baud rate from a external source. I am able to receive the new baud rate which I need to set it to the port.
How do I set the new baud rate to the port? Should I pthread_exit() the receiving thread, initialize the UART port and then start the thread again?
Or should I just close the fd and initialize the UART port with the new baud rate without exiting from the thread?
Or is there any other simple way or function to set the UART to the port ?
My Initialization code:
int Gpsfd;
struct termios Gps_termios, Gps_old;
void GpsPortInit(void)
{
char path[12] = "/dev/ttyS4";
//open GSM_termios for tx/rx
Gpsfd = open(path, O_RDWR | O_NOCTTY);
if (Gpsfd < 0)
printf("port failed to open\n");
//save current attributes
tcgetattr(Gpsfd, &Gps_old);
bzero(&Gps_termios, sizeof(Gps_termios));
Gps_termios.c_cflag = CLOCAL | CREAD | CS8;
if (!strcmp(g_sParameters.RS232BaudRate, "9600"))
{
Gps_termios.c_cflag |= B9600;
}
else if (!strcmp(g_sParameters.RS232BaudRate, "19200"))
{
Gps_termios.c_cflag |= B19200;
}
else if (!strcmp(g_sParameters.RS232BaudRate, "57600"))
{
Gps_termios.c_cflag |= B57600;
}
else if (!strcmp(g_sParameters.RS232BaudRate, "115200"))
{
Gps_termios.c_cflag |= B115200;
}
Gps_termios.c_iflag = IGNPAR;
Gps_termios.c_oflag = 0;
Gps_termios.c_lflag = 0;
Gps_termios.c_cc[VTIME] = 0;
Gps_termios.c_cc[VMIN] = 1;
//clean the line and set the attributes
tcflush(Gpsfd, TCIFLUSH);
tcsetattr(Gpsfd, TCSANOW, &Gps_termios);
}
This is the baudrate change function suggested below:
int set_baudrate(speed_t speed)
{
//struct termios tty;
if (tcgetattr(Gpsfd, &Gps_termios) < 0) {
printf("Error from tcgetattr1: %s\n", strerror(errno));
return -1;
}
cfsetospeed(&Gps_termios, speed);
cfsetispeed(&Gps_termios, speed);
if (tcsetattr(Gpsfd, TCSANOW, &Gps_termios) != 0) {
printf("Error from tcsetattr: %s\n", strerror(errno));
return -1;
}
tcflush(Gpsfd, TCIOFLUSH); /* discard buffers */
return 0;
}
I am Initializing the UART once with GpsPortInit and after that if I get any request to change the baudrate I change it with set_baudrate.
How do I set the new baud rate to the port?
In Linux, userspace does not have direct access to hardware such as a UART, so your program is constrained to use the serial terminal and the termios API.
That is confirmed by your use of /dev/ttyS4 (rather than a device node named /dev/uart4).
Should I pthread_exit() the receiving thread, initialize the UART port and then start the thread again?
That should not matter.
The pthread is merely "reading" from the termios buffer, rather than directly accessing any hardware.
However your program needs to be robust and able to cope with possible mangled messages.
Or should I just close the fd and initialize the UART port with the new baud rate without exiting from the thread?
Closing the file descriptor would deny your program further access to the serial terminal, so that does not make sense.
Userspace does not have direct access to hardware such as a UART, so your program is constrained to use the termios API.
Or is there any other simple way or function to set the UART to the port ?
Use the termios API to change the baudrate of the serial terminal, e.g.:
int set_baudrate(int fd, speed_t speed)
{
struct termios tty;
int rc1, rc2;
if (tcgetattr(fd, &tty) < 0) {
printf("Error from tcgetattr: %s\n", strerror(errno));
return -1;
}
rc1 = cfsetospeed(&tty, speed);
rc2 = cfsetispeed(&tty, speed);
if ((rc1 | rc2) != 0 ) {
printf("Error from cfsetxspeed: %s\n", strerror(errno));
return -1;
}
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
printf("Error from tcsetattr: %s\n", strerror(errno));
return -1;
}
tcflush(fd, TCIOFLUSH); /* discard buffers */
return 0;
}
The above should be similar to the initialization that you used to "initialize the UART port (baud rate, parity, stop bits etc.) ... using the termios library".
That is, the code above adheres to Setting Terminal Modes Properly
.
Note that the tcsetattr() call could use the argument TCSAFLUSH instead of TCSANOW. That would "change attributes when output has drained; also flush pending input" according to the termios.h man page.
However waiting for the output to drain seems unnecessary, as changing the baudrate implies the current baudrate is incompatible with the other end of the serial link. IOW garbage is presumably being transmitted and garbage is presumably being received.
So immediately change the baudrate (i.e. use TCSANOW), and then discard the contents of the receive buffer.
Addendum: review of your initialization code
Your initialization code is low quality, is not portable, and may not reliably setup the serial terminal.
Starting with a zeroed-out termios structure (instead of the existing values) is contrary to recommended practice as described in Setting Terminal Modes Properly
and Serial Programming Guide for POSIX Operating Systems.
See this answer for concise code for proper serial terminal initialization to blocking non-canonical mode.
This is the baudrate change function suggested below:
Note that the routine has been updated in my answer to enhance error reporting.
You neglect to show exactly how you use/call this routine.
I am Initializing the UART once with GpsPortInit and after that if I get any request to change the baudrate I change it with set_baudrate.
How do you coordinate any baudrate changes with the other end of the serial link?
You also neglect to describe your test procedures, what baudrates you are using, how the other end is setup.
Any of these omissions could reveal the reason(s) why this set_baudrate() routine "is not working" for you.

Sending data over serial port

I am trying to send data over serial port programmatically. I have an embedded system which runs Linux. On the bootloader prompt, I intend to stop execution by pressing CTRL+C and modifying the boot args. I am able to send the CTRL+C character and the booting does stop. However when I try to send a big command over serial port using the write(), it doesn't seem to be succeeding. Any inputs on what could be wrong? Below is my code snippet:
int main() {
// Open the serial port. Change device path as needed (currently set to an standard FTDI USB-UART cable type device)
int serial_port = open("/dev/ttyUSB0", O_RDWR);
// Create new termios struc, we call it 'tty' for convention
struct termios tty;
char data = 0x03;
char data2[]={'r','u','n',' ', 'l','o','a','d','b','o','o','t','e','n','v','\r'};
char data4[]= "setenv mmcargs \'setenv bootargs console=ttyO0,115200n8 console=tty0 ${optargs}
root=/dev/mmcblk1p3 rootfstype=ext4 panic=1\';
// Read in existing settings, and handle any error
if(tcgetattr(serial_port, &tty) != 0) {
printf("Error %i from tcgetattr: %s\n", errno, strerror(errno));
return 1;
}
tty.c_cflag = CS8 | CLOCAL | CREAD;
tty.c_iflag = IGNBRK | IGNPAR;
tty.c_oflag = OPOST;
tty.c_lflag = 0;
tty.c_cc[VMIN] = 1;
tty.c_cc[VTIME] = 0;
tcflush(serial_port, TCIFLUSH);
// Set in/out baud rate to be 115200
cfsetispeed(&tty, B115200);
cfsetospeed(&tty, B115200);
// Save tty settings, also checking for error
if (tcsetattr(serial_port, TCSANOW, &tty) != 0) {
printf("Error %i from tcsetattr: %s\n", errno, strerror(errno));
return 1;
}
write(serial_port, &data, 1);
ret = write(serial_port, &data2, sizeof(data2));
ret = write(serial_port, &data4, sizeof(data4));
All the writes succeed (this i verified by returned value) but then the write corresponding to data4 doesn't succeed in generating a response. When I simultaneously run minicom, I see that only 27 bytes of the final write are actually written. Not sure what is wrong.
If I give the same command as in data4 using putty, it succeeds without any issue. The settings on putty were speed(baud): 115200
Databits: 8
Stopbits: 1
Parity: NONE
Flowcontrol: NONE
Can someone help me where I am going wrong?
Thanks!

Read serial port with inter-character timeout for long streams

in my application i am connected to serial device which will send periodic data whose size is unknown. I do not want to detect incoming frame using StartofFrame and EndOfFrame keywords. Instead of this, i want to detect incoming frame using inter-character timeout. There always will be some duration between incoming data frames at least 100ms.
So, i can detect frames using this time interval. I am opening serial port using this function:
uint8_t open_serial_port(uart_device* dev)
{
dev->fd = open(dev->path, O_RDWR | O_NOCTTY);
if (dev->fd < 0)
{
syslog(LOG_CRIT, "Serial Port {%s}-{%s} couldn't opened", dev->name, dev->path);
return EXIT_FAILURE;
}
syslog(LOG_INFO, "Serial Port {%s}-{%s} opened with fd {%d}", dev->name, dev->path, dev->fd);
struct termios tty;
if (tcgetattr(dev->fd, &tty) < 0)
{
syslog(LOG_CRIT, "Serial Connection attributes get for fd {%d} failed with errno cause {%s}", dev->fd, strerror(errno));
return EXIT_FAILURE;
}
cfmakeraw(&tty);
cfsetospeed(&tty, (speed_t) dev->speed);
cfsetispeed(&tty, (speed_t) dev->speed);
tty.c_cc[VMIN] = 255; //! This is used to block to get at least VMIN characters
tty.c_cc[VTIME] = 10; //! used to block for tens of seconds timeout 10 -> 1 seconds timeout
tty.c_cflag |= CLOCAL;
tty.c_cflag &= ~CSIZE; //! Mask the character size bits
tty.c_cflag |= get_start_stop_opt(dev->data, dev->stop); //! Set data, stop bits
tty.c_cflag |= get_parity_opt(dev->parity); //! set parity configuration
tty.c_cflag |= get_hw_flow_control(dev->flow_control);
if (tcsetattr(dev->fd, TCSANOW, &tty))
{
syslog(LOG_CRIT, "Serial Connection attributes set for fd {%d} failed with errno cause {%s}", dev->fd, strerror(errno));
return EXIT_FAILURE;
}
syslog(LOG_INFO, "Serial Connection {%s}-{%s} established. speed: %d, parity: %s, data: %d, stop: %d, flw_ctrl: %d", dev->name, dev->path, dev->speed,
dev->parity, dev->data, dev->stop, dev->flow_control);
return EXIT_SUCCESS;
}
I am reading serial port in blocking mode:
readSize = read(dev->fd, &buf, UART_MAX_PAYLOAD);
if (readSize < 0)
{
syslog(LOG_CRIT, "UART Read failed while reading {%s}, error {%s}", dev->path, strerror(errno));
usleep(1000 * 1000);
continue;
}
syslog(LOG_INFO, "Uart read completed. readSize: %d", readSize);
Actually VMIN and VTIME parameters of termios is suitable for my situation but VMIN and VTIME is defined as 8 bit. So, i can state maximum of 255 characters to read.
So, when device sends 2048 bytes of data, i read this as :
Uart read completed. readSize: 496
Uart read completed. readSize: 496
Uart read completed. readSize: 496
Uart read completed. readSize: 496
Uart read completed. readSize: 64
Somehow, i can not read whole data at same time.
What is the problem about reading large data in blocking mode? How can i read large serial data in one time?
Best regards.

Embedded Linux: Reading bytes from serial port too slow for quicker transfer

I have a microcontroller and an embedded PC. These two communicated via a serial connection and both are receiving and sending data to each other. The baud rate between these two is 38400. Microcontroller and PC have both the same configuration to ensure the communication (8 data bits, 1 stop bit, even parity).
Communication works fine until the microcontroller starts sending messages around every 10 ms. At this point, the sending queue of the microcontroller gets full and overruns. He then sends an error message to the PC (this is how I know, that the sending queue of the microcontroller get overrun, not that of the PC.
Prior to the embedded Linux version of the PC program, the microcontroller had run with a DOS version of the PC program without causing an error. In DOS single bytes are directly read from and written to the serial port (no kernel buffer like in Linux) Since most of the C-code is portable to Linux I try to replicate the DOS behavior of serial port reading and writing in Linux to keep the rest of which processes these single bytes.
I open and initialize the serial port in the PC as follows.
fd_mc = open("/dev/ttyS1", O_NOCTTY | O_RDWR /*| O_NONBLOCK*/ /*| O_SYNC*/);
if(fd_mc == -1)
{
perror("Could not open µc port.");
}
else
{
struct termios tty;
memset(&tty, 0, sizeof(tty));
if ( tcgetattr ( fd_mc, &tty ) != 0 )
{
perror("Error getting termios attributes");
}
cfsetospeed (&tty, B38400);
cfsetispeed (&tty, B38400);
tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); //raw input
tty.c_oflag &= ~OPOST; //raw output
tty.c_cflag |= PARENB; //even parity
tty.c_cflag &= ~PARODD;
tty.c_cflag &= ~CSTOPB;
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8;
tty.c_cflag |= (CLOCAL | CREAD);
tcsetattr(fd_mc, TCSANOW, &tty);
}
The code above is a snippet from a function which initializes two serial ports (one of them is the one to the microcontroller).
Edit:
Here is the setting of the flow control, there is none.
tty.c_cflag &= ~CRTSCTS; //No hardware based flow control
tty.i_cflag &= ~(IXON | IXOFF | IXANY); //no software based flow control
Reading from the serial port happens inside a thread with the help of a ring buffer and pollin. This thread is being called inside the main loop. Code below:
void *thread_read()
{
struct sched_param param;
param.sched_priority = 97;
int ret_par = 0;
ret_par = sched_setscheduler(3, SCHED_FIFO, &param);
if (ret_par == -1) {
perror("sched_setscheduler");
return 0;
}
struct pollfd poll_fd[2];
int ret;
extern struct fifo mc_fifo, serial_fifo;
ssize_t t;
char c;
poll_fd[0].fd = fd_mc;
poll_fd[0].events = POLLIN;
poll_fd[1].fd = fd_serial;
poll_fd[1].events = POLLIN;
while(1) {
ret = poll(poll_fd, 2, 10000);
if(ret == -1) {
perror("poll");
}
if(poll_fd[0].revents & POLLIN) {
t = read(fd_mc, &c, 1);
if(t>0) {
fifo_in(&mc_fifo, c);
}
}
if(poll_fd[1].revents & POLLIN) {
t = read(fd_serial, &c, 1);
if(t>0) {
fifo_in(&serial_fifo, c);
}
}
}
pthread_exit(NULL);
}
Call inside the main loop.
pthread_t read;
pthread_create(&read, NULL, thread_read, NULL);
The function for writing inside the buffer (fifo_in) is
int fifo_in(struct fifo *f, char data)
{
if( (f->write + 1 == f->read) || (f->read == 0 && f->write + 1 == FIFO_SIZE) ) //checks if write one before read or at the end
{
printf("fifo in overflow\n");
return 0; //fifo full
}
else {
f->data[f->write] = data;
f->write++;
// printf("Byte in: Containing %4d\tData:\t%4d\n", BytesInReceiveBuffer(1), data); //Bytes contained in fifo of mc
if(f->write >= FIFO_SIZE) {
f->write = 0;
}
return 1; //success
}
}
What this function basically does is check where the read and write positions are and write data inside the buffer if the two positions don't overlap and are more than +1 away from each other.
When another function needs the bytes inside the ring buffer it calls GetByte which reads the bytes from the ring buffer.
GetByte
int GetByte(int port)
{
char c;
switch(port) {
case 0: //COM1
fifo_out(&serial_fifo,&c);
break;
case 1: //COM2
fifo_out(&mc_fifo,&c);
break;
}
return (int)c;
}
fifo_out
int fifo_out(struct fifo *f, char *data) {
if(f->read == f->write) {
printf("fifo in overfwrite\n");
*data = 0;
return 0;
}
else {
*data = f->data[f->read];
f->read++;
// printf("Byte out: Containing %4d\tData:\t%4d\n", BytesInReceiveBuffer(1), *data);
if(f->read >= FIFO_SIZE) {
f->read = 0;
}
return 1;
}
}
Prior to the Linux porting, everything was sequential in the DOS version.
Ate the moment my best guess is, that read() is to slow and at some point you and starts slowing down the reading from the buffer, which again, blocks the calling of the thread. Maybe I am wrong. At the moment I am kinda clueless what exactly the bug is or even how to fix this.
Every good advice is appreciated.
Are you sure you need a fifo in the application? Your serial driver most likely already has quite a large buffer in the kernel (often a page, which is usually 4kB). If this suffices you could radically simplify the implementation by having GetByte doing a non-blocking read against the serial device.
If you want to stick with this design, consider reworking your read-loop to read more than byte at a time. As it is now you need two syscalls for every byte read.
You're changing the scheduling class for PID 3, always. This is probably not what you want. Also, this only means that your thread will run once the bytes have landed in the kernels internal buffer (i.e. when poll returns). If the bytes are transferred from the hardware FIFO to the buffer by running a job on a workqueue, and that workqueue runs in SCHED_OTHER (which most them do), changing the scheduler of your thread will not have the desired effect. Classic priority inversion problem. You might want to audit the kernel driver used by your particular board. Though, if you empty the entire buffer on every read this should be less of a problem.
If this code is ever used on an SMP system, you're most like going to want to guard the read and write pointers with a lock since they are not updated atomically. Threads are hard to get right, have you considered using an event-loop instead? Something like libev.

Resources