1
votes

I have a custom USB cdc-acm device which sends images to the computer thanks to serial communication. The kernel driver works fine as the device appears as /dev/ttyACM0 and I am able to send commands and get data using open, write and read functions.

As the device continuously sends data, I get this data in a separate thread in a while loop:

while (m_isListening)
{
    int rd = read(m_file_descriptor, read_buffer, 512);

    // Display read data
    if (rd > 0)
    {
        std::cout << rd << " bytes read: ";
        for (int i = 0; i < rd; i++)
        {
            printf(" %x", read_buffer[i]);
        }
        std::cout << " # END" << std::endl;
    }
    // Nothing was to be read
    else if (rd == 0)
    {
        std::cout << "No bytes read =(" << std::endl;
    }
    // Couldn't acces the file for read
    else
    {
        printf("%d %s \n", errno, strerror(errno));
    }
}
std::cout << std::endl;

I manage to get data read and displayed, but I also have a lot of line like that (11 = EAGAIN error):

11 Ressource Temporarily Unavailable

So I have a couple of questions:

  • How fast can I / should I access (read in) the tty file ?
  • Does the EAGAIN error mean I can't read in the file while the device write into it?
  • If yes, as a consequence does it mean that while I'm reading in the file, the device can't write into it? So does data get lost?
  • Finally (and more subjective question), am I doing something really dirty with this kind of code? Any comments or suggestions about accessing real time data through tty file?

Thanks.

EDIT

Some more precisions: the purpose of this code is to have a C/C++ interface with the device under linux only. I know that the device is sending frames around 105 kbytes at a particular frame rate (but the code should not depend on this framerate and be smooth at 15 fps as well as at 1 fps... no lag).

As this is my first time writing this kind of code and I don't understood evrything from the man pages I found, I kept default settings from the sample I found for most of the code (opening and reading):

Opening:

// Open the port
    m_file_descriptor = open("/dev/ttyACM0", O_RDWR | O_NOCTTY | O_NDELAY);
    if (m_file_descriptor == -1)
    {
        std::cerr << "Unable to open port " << std::endl;
}

The only settings modification I did before reading was:

fcntl(m_file_descriptor, F_SETFL, FNDELAY);

... in order to set the reading non blocking (FNDELAY is equivalent as O_NONBLOCK if I understood well).

3
One question at a time, please.Jesper Juhl
@JesperJuhl those questions (except the last one which is more subjective) are all linked and related to each other, should I really open 3 topics for that?Pierre Baret
"am I doing something really dirty with this kind of code?" -- Well it's (unnecessarily) CPU intensive/inefficient and incomplete. You've shown no termios code to configure the terminal. You can use whatever settings that already exist, but that's poor coding. And since you use nonblocking I/O, a lot of termios settings are ignored. "...accessing real time data through tty file?" -- The received data is fully buffered. The "fastest" method of "reading" this buffered data is efficient use (i.e. the fewest number) of syscalls. Typically that means using blocking I/O.sawdust
@sawdust I've shown no termios code cause I have not used any. Everything is default params and simplest code I found and understood on the web. If you have something more advanced to suggest, feel free to write an answer with explanations, I would really like to understand the most of what you just threw here.Pierre Baret

3 Answers

1
votes

/dev/ttyACM0 is (likely) a serial port, and it has a limited speed which depends on the baud rate it is running at. So regarding your questions:

How fast can I / should I access (read in) the tty file ?

Entirely depends on the baud rate of the port.

Does the EAGAIN error mean I can't read in the file while the device write into it?

Eagain means try again later. So right now the serial port has no data in its buffer, but if you try again later it may have some data.

If yes, as a consequence does it mean that while I'm reading in the file, the device can't write into it? So does data get lost?

No. As long as you are reading as fast or faster than the device sending data is writing (which you are, since you get EAGAIN), you won't lose data.

Finally (and more subjective question), am I doing something really dirty with this kind of code? Any comments or suggestions about accessing real time data through tty file?

Nothing dirty here that I can see.

1
votes

How fast can I read in /dev/ttyACM0 file?

That depends on numerous factors, including the the processor load, priority of your process/thread, the efficiency of your code, and (of course) the receive rate of the data.

I have a custom USB cdc-acm device which sends images to the computer thanks to serial communication. The kernel driver works fine as the device appears as /dev/ttyACM0 ...

So you have a USB gadget that communicates through an emulated serial port.
Your Linux program accesses this device as a serial terminal.

How fast can I / should I access (read in) the tty file ?

Your program could/does make nonblocking read() syscalls more frequently than the data actually is received, but that would result in EAGAIN or EWOULDBLOCK return codes and be inefficient.
For typical baudrates (e.g. much less than megabps) and the typical processor running a Linux kernel, your program will probably have to wait for data, and should use blocking read() syscalls for improved efficiency.
Since you have a dedicated thread for input and no calculations/processing (in this thread) to perform while waiting for the data, then blocking I/O is certainly the prudent method.

Does the EAGAIN error mean I can't read in the file while the device write into it?

You seem to be cognizant of the intermediate tty buffer, but it's hard to be sure.
The EAGAIN should be interpreted as: "the request for data cannot be fulfilled at this time, try again later". The typical reason for EAGAIN is simply that there is no data in the buffer for "reading".
Whether the device driver is holding a lock on the buffer need not be a concern.

If yes, as a consequence does it mean that while I'm reading in the file, the device can't write into it? So does data get lost?

The tty subsystem will treat its buffers as a critical resource. You need not be concerned about contention over this buffer. The device driver has its own buffer(s) distinct from the tty buffer, for receiving data from the device. See Figure 3 of Serial Drivers.
The one mechanism for losing data that you have control over would be buffer overrun, i.e. if your program does not read() as fast as the data is received, then the tty buffer will eventually be filled and then be overrun, which results in lost data.
Note that the tty buffer is typically 4KB.

Finally (and more subjective question), am I doing something really dirty with this kind of code?

Well your code is (unnecessarily) CPU intensive/inefficient and incomplete.
Repetitive non-blocking read() syscalls in a loop are a waste of CPU cycles whenever the return code is EAGAIN.

You've shown no termios code to configure the terminal. You can use whatever settings that already exist, but that's poor coding. Your program will fail to read anything whenever the baudrate and/or line setttings are changed, or you'll have this problem.
A well-written program that uses a serial terminal will always initialize that terminal to the configuration that it expects/requires.
For sample code see this answer and this answer

Any comments or suggestions about accessing real time data through tty file?

Linux is not a realtime OS, although there are "realtime" patches to make latencies more deterministic. But you have not expressed any requirements that exceed prior embedded Linux projects using typical HW.
The techniques for fast and robust serial terminal transfers do not rely on nonblocking I/O, which is likely to incur excessive processing overhead (especially when done poorly) to the detriment of other processes/threads.
Blocking non-canonical terminal I/O has many options to specify when the read() should be satisfied and return to the caller. See this answer for details.

To minimize read() syscalls yet scan/parse received data a byte at a time, buffer the data in your program, e.g. sample code

0
votes

Well, first of all, relying on how fast data should be read/written to avoid starvation or denial of service is a bad idea, because the speed can vary due to many factors (unless you can guarantee certain deterministic response times). It is better to understand why you're getting that error and how to handle it robustly. This would depend on the OS you're using. Since you have "linux" among the question tags, I will assume you're using Linux. From the man pages (see man(2) read):

   EAGAIN The file descriptor fd refers to a file other than a socket and has been marked non-
          blocking (O_NONBLOCK), and the read would block.  See open(2) for further details on
          the O_NONBLOCK flag.

   EAGAIN or EWOULDBLOCK
          The file descriptor fd refers to a socket and has been  marked  nonblocking  (O_NON-
          BLOCK),  and  the read would block.  POSIX.1-2001 allows either error to be returned
          for this case, and does not require these constants to have the  same  value,  so  a
          portable application should check for both possibilities.

Now, you aren't showing us how you opened the device or what possible fcntrl options you set. I am assuming that the device is nonblocking (achieved by setting O_NONBLOCK flag on the file descriptor), or that the device driver implements nonblocking I/O by default.

In that case, EAGAIN and EWOULDBLOCK indicate that no data is currently present, and read() would normally block at this point until data becomes available.

An easy solution, not taking into account any other requirements (because you haven't stated any) is to check for EAGAIN (and EWOULDBLOCK for portability), and in case either occurs, sleep for some number of milliseconds before you resume. Here is your code slightly modified:

while (m_isListening)
{
    errno = 0;
    int rd = read(m_file_descriptor, read_buffer, 512);

    // Display read data
    if (rd > 0)
    {
        std::cout << rd << " bytes read: ";
        for (int i = 0; i < rd; i++)
        {
            printf(" %x", read_buffer[i]);
        }
        std::cout << " # END" << std::endl;
    }
    // Nothing was to be read
    else if (rd == 0)
    {
        std::cout << "No bytes read =(" << std::endl;
    }
    // Couldn't acces the file for read
    else
    {
        if (errno == EAGAIN || errno == EWOULDBLOCK)
        {
            usleep(2000); // Sleep for 2 milliseconds.
        }
        else
        {
            printf("%d %s \n", errno, strerror(errno));
        }
    }
}
std::cout << std::endl;

By the way, depending on how the device driver handles read(), the clause if (rd == 0) may be redundant. I say this depends, because in some cases 0 is returned to indicate end of file. This may or may not be treated as error. For example, in case of TCP/IP, if read() returns 0, then this means that peer closed the connection.

EDIT

Reflecting the edits to the original question: yes, O_NDELAY is synonymous with O_NONBLOCK (in fact, you do not need that extra call to fcntrl to set O_NDELAY because you're already opening the device with that option set). So that means your device is non-blocking, so if data is not available, rather than blocking and waiting for data to arrive, the driver throws EAGAIN (EWOULDBLOCK would also be legal).

If you do not have strict time constraints and can tolerate blocks, you can simply remove the O_NDELAY option. Otherwise, do what I suggested above.

Regarding loss of data: if O_NDELAY is not set (or, equivalently, O_NONBLOCK), read() will return as soon as data becomes available (but will not wait to fill the buffer up to the number of requested bytes -- third parameter in the call to read() -- and rather return the number of bytes available, up to the specified requested number). However, if data is not available, it will block (again, assuming that's the driver's behavior). If you want it to return 0 instead -- well, this is up to the driver. This is exactly the reason O_NONBLOCK option is provided.

One disadvantage here is that unless the driver provides some mechanism of controlling delays, there is no telling for how long the device might block (subject to how quickly data arrives). This is the reason that one typically sets O_NONBLOCK and then manually controls read() delays (e.g. under real-time requirements).