user2138149
user2138149

Reputation: 17230

File descriptor is in blocking mode, but read() is not blocking

I am writing some software to handle serial port read/writing for a Beaglebone system. The OS is Debian 9. I am writing code in C with --std=gnu99.

Here is my code:

// reference
// https://www.cmrr.umn.edu/~strupp/serial.html

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <sys/ioctl.h>

int open_port(void)
{    
    int fd;

    fd = open("/dev/ttyS1", O_RDWR | O_NOCTTY | O_NDELAY);
    // removing O_NDELAY no difference
    if(fd == -1)
    {
        perror("open_port: Unable to open /dev/ttyS1 - ");
    }
    else
    {
        fcntl(fd, F_SETFL, 0);
    }

    return fd;
}

int main()
{

    // open fd for serial port
    int fd = open_port();


    // set baud rate
    struct termios options;
    // get current options
    tcgetattr(fd, &options);
    // set input and output baud
    cfsetispeed(&options, B115200);
    cfsetospeed(&options, B115200);
    // enable reciever and set local mode
    // CLOCAL: ignore modem control lines
    // CREAD: enable reciever
    options.c_cflag |= (CLOCAL | CREAD);

    // set partity bit
    //options.c_cflag &= PARENB;
    options.c_cflag &= ~PARENB;
    // use even parity
    //options.c_cflag &= ~PARODD;
    // use only 1 stop bit
    options.c_cflag &= ~CSTOPB;
    // set character size to 8 bit
    options.c_cflag &= ~CSIZE;
    options.c_cflag &= CS8;
    // disable flow control
    //options.c_cflag &= ~CNEW_RTSCTS; // does not work?

    // note: check local options, some may be required
    // disable canonical input (use raw)
    // disable echoing of characters
    // disable echoing of erase characters
    // disable signals SIGINTR SIGSUSP SIGDSUSP, SIGQUIT
    // input characters are passed through exactly as recieved
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

    // disable parity check
    options.c_iflag &= IGNPAR;

    // disable flow control (software)
    options.c_iflag &= ~(IXON | IXOFF | IXANY);

    // set raw output, no output processing
    options.c_oflag &= ~OPOST;

    // set options
    tcsetattr(fd, TCSANOW, &options);

    char data[] = {0x01, 0x03, 0x00, 0x00};
    int n = write(fd, data, 4);
    if(n =! 4)
    {
        printf("write fail %d\n", n);
    }

    char buffer[40];
    n = read(fd, buffer, 2);
    if(n != 2)
    {
        printf("read fail %d\n", n);
    }
    if(buffer[0] == 0x03 && buffer[1] == 0x00)
    {

    }
    else
    {
        printf("uart error\n");
    }

    char data2[] = {0x00, 0x00, 0x00, 0x01};
    n = write(fd, data2, 4);
    if(n != 4)
    {
        printf("write fail %d\n", n);
    }

    n = read(fd, buffer, 2);
    if(n != 2)
    {
        printf("read fail %d\n", n);
    }
    if(buffer[0] == 0x03 && buffer[1] == 0x00)
    {

    }
    else
    {
        printf("uart error\n");
    }

    printf("process complete\n");

    // to close
    close(fd);
}

The issue I have is calls to read() do not block. This is not the desired behaviour. These calls should block until 2 bytes of data are received.

My guess would be I have misconfigured an option somewhere. However I don't know where the mistake is, and from what I have researched, this should be reading in blocking mode. (fcntl(fd, F_SETFL, 0);)

Upvotes: 1

Views: 1211

Answers (1)

sawdust
sawdust

Reputation: 17067

The issue I have is calls to read() do not block.

That's actually a conclusion, rather than an observation.
Presumably your program is reporting zero bytes read, which technically is not an error.

... from what I have researched, this should be reading in blocking mode. (fcntl(fd, F_SETFL, 0);)

Correct, you have the file descriptor configured for blocking mode.

My guess would be I have misconfigured an option somewhere.

Yes, your termios configuration is incomplete. (It uses proper bitwise operations, but unfortunately is incomplete and has errors, see ADDENDUM.)
When configured for non-canonical mode, you must also specify the VMIN and VTIME parameters.
When left unspecified (as in your code), the default values of VMIN = 0 and VTIME = 0 are likely to be in effect, which is equivalent to a non-blocking read.


Note that the byte length in the read() syscall has minimal effect on completion behavior (other than capping the number of bytes to be returned).
From the man page for read(2):

read() attempts to read up to count bytes from file descriptor fd into the buffer starting at buf.

Therefore the count parameter is effectively a maximum.
The actual number of bytes read can be between zero and count depending on the values configured for VMIN and VTIME (and the actual data available).

See Linux Blocking vs. non Blocking Serial Read for more details.


To read at least two bytes per syscall (with no time limit), include the following in the termios configuration:

options.c_cc[VMIN] = 2;
options.c_cc[VTIME] = 0;

The above two statements inserted into your program before the tcsetattr() syscall would cause your program to block forever until exactly two bytes can be returned in each read(fd, buffer, 2) statement.
A read(fd, buffer, 4) statement would also block forever, and return either 2, 3, or 4 bytes of data (depending on program timing with respect to data reception & buffering).


ADDENDUM

Turns out there are a few bugs in your termios initialization.
As posted your program neither transmits or receives any data correctly, and you failed to detect these errors or neglect to mention them.

The following statement in your program obliterates all the previous c_cflag operations, and unintentionally reconfigures the baudrate to B0 and the character size to CS5:

    options.c_cflag &= CS8;

The correct syntax is:

    options.c_cflag |= CS8;

The statement

    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

should be expanded with more attributes to match cfmakeraw():

    options.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);

The following statement in your program disables every attribute in c_iflag except IGNPAR, and leaves the IGNPAR attribute in an unknown/ambiguous state:

    // disable parity check
    options.c_iflag &= IGNPAR;

To match cfmakeraw(), it should be changed to:

    options.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
                       | INLCR | IGNCR | ICRNL);

With all the corrections mentioned, I can get your program to execute as expected.

Upvotes: 2

Related Questions