• Embedded linux serial port

    From pozz@21:1/5 to All on Wed May 26 13:53:59 2021
    I used a simple driver for serial port in Linux with success in many
    projects, but recently I found an issue with it.

    The scenario is an embedded Linux (running on iMX6) that runs a QT
    application (that creates a GUI on a touch display) and a C application
    that communicates over a serial port.

    When QT application starts some complex graphics (I see its CPU load
    reaching 70-80%), the serial port application stops working correctly.
    After some debugging, I noticed that the bytes really transmitted on the
    wire aren't correct. It seems one or two bytes are re-transmitted and
    one-two bytes aren't completely transmitted.

    I suspect my serial port driver has some errors that are triggered only
    when the CPU is loaded by other processes, but I don't know how to fix them.

    I use O_NONBLOCK flag when opening serial port, but write() always
    returns a positive number, so I think the byte is correctly moved to the low-level serial port driver and really transmitted... but it isn't
    always the case.

    If I enable debugging messagin (with DEBUG_SERIAL macro), I always see
    correct data on stdout, but wrong data on the wire.

    Any hint?




    #include <stdint.h>
    #include <stdbool.h>
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <termios.h>
    #include <time.h>
    #include "serial.h"

    //#define DEBUG_SERIAL

    #ifdef DEBUG_SERIAL
    static bool debug_tx;
    #endif

    SERIAL_HANDLE
    serial_open(const char *serial_name, int baudrate)
    {
    if (serial_name == NULL) return INVALID_SERIAL_HANDLE_VALUE;

    speed_t speed;
    if (baudrate == 57600)
    {
    speed = B57600;
    }
    else if (baudrate == 115200)
    {
    speed = B115200;
    }
    else
    {
    return INVALID_SERIAL_HANDLE_VALUE;
    }

    int hSerial;

    hSerial = open(serial_name, O_RDWR | O_NOCTTY | O_NONBLOCK);
    if (hSerial != -1)
    {
    int oldflags = fcntl (hSerial, F_GETFL, 0);
    if (oldflags == -1) return -1;
    oldflags &= ~O_NONBLOCK;
    if (fcntl(hSerial, F_SETFL, oldflags) == -1)
    {
    return INVALID_SERIAL_HANDLE_VALUE;
    }

    struct termios options;
    tcgetattr(hSerial, &options);

    cfsetispeed(&options, speed);
    cfsetospeed(&options, speed);

    /* Input mode flags */
    options.c_iflag &= ~(ISTRIP | IGNCR | ICRNL | INLCR | IXOFF |
    IXON | INPCK );
    options.c_iflag |= IGNBRK;

    /* Output mode flags */
    options.c_oflag &= ~OPOST;

    /* Control mode flags */
    options.c_cflag |= (CLOCAL | CREAD);
    options.c_cflag &= ~( CSTOPB | PARENB);
    options.c_cflag &= ~CSIZE;
    options.c_cflag |= CS8;

    /* Local mode flags */
    options.c_lflag &= ~(ICANON | ECHO | ISIG);
    options.c_lflag = NOFLSH;
    options.c_cc[VMIN] = 0;
    options.c_cc[VTIME] = 1;

    tcsetattr(hSerial, TCSANOW, &options);

    #ifdef DEBUG_SERIAL
    debug_tx = 1;
    printf("-> ");
    #endif
    }

    return (SERIAL_HANDLE)hSerial;
    }

    void
    serial_close(SERIAL_HANDLE hSerial)
    {
    close(hSerial);
    }

    int
    serial_getdata(SERIAL_HANDLE hSerial, void *data, size_t data_len,
    unsigned int timeout_sec)
    {
    size_t bytes_read = 0;
    uint8_t *d = data;
    time_t t_begin = time(NULL);

    while(bytes_read != data_len)
    {
    if ((timeout_sec > 0) && (time(NULL) - t_begin > timeout_sec)) {
    break;
    }

    int ret;
    ret = read(hSerial, d, data_len - bytes_read);
    if (ret < 0) return -1; // Error
    if (ret > 0) {
    #ifdef DEBUG_SERIAL
    if (debug_tx) {
    printf("\n<- ");
    fflush(stdout);
    debug_tx = 0;
    }
    for (unsigned int i = 0; i < ret; i++) {
    printf("%02X", ((unsigned char * ) d)[i]);
    fflush(stdout);
    }
    #endif

    d += ret;
    bytes_read += ret;
    }
    }


    return bytes_read;
    }

    int
    serial_putchar(SERIAL_HANDLE hSerial, unsigned char c)
    {
    #ifdef DEBUG_SERIAL
    if (!debug_tx) {
    printf("\n-> ");
    debug_tx = 1;
    }
    printf("%02X", c);
    #endif
    int bytes_written;

    bytes_written = write(hSerial, &c, 1);
    if (bytes_written != 1) return -1;

    return 0;
    }

    int
    serial_putdata(SERIAL_HANDLE hSerial, const void *data, size_t size)
    {
    const unsigned char *d = data;
    while(size--) {
    int ret;
    ret = serial_putchar(hSerial, *d++);
    if (ret < 0) return -1;
    }
    return 0;
    }

    int
    serial_putstr(SERIAL_HANDLE hSerial, const char *s)
    {
    if (s == NULL) return -1;
    return serial_putdata(hSerial, s, strlen(s));
    }

    #endif

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Johann Klammer@21:1/5 to pozz on Wed May 26 23:52:16 2021
    On 05/26/2021 01:53 PM, pozz wrote:
    I used a simple driver for serial port in Linux with success in many projects, but recently I found an issue with it.

    The scenario is an embedded Linux (running on iMX6) that runs a QT application (that creates a GUI on a touch display) and a C application that communicates over a serial port.

    When QT application starts some complex graphics (I see its CPU load reaching 70-80%), the serial port application stops working correctly.
    After some debugging, I noticed that the bytes really transmitted on the wire aren't correct. It seems one or two bytes are re-transmitted and one-two bytes aren't completely transmitted.

    I suspect my serial port driver has some errors that are triggered only when the CPU is loaded by other processes, but I don't know how to fix them.

    I use O_NONBLOCK flag when opening serial port, but write() always returns a positive number, so I think the byte is correctly moved to the low-level serial port driver and really transmitted... but it isn't always the case.

    If I enable debugging messagin (with DEBUG_SERIAL macro), I always see correct data on stdout, but wrong data on the wire.

    Any hint?



    [I didn't read your code]
    But here's what I used for transmitting.

    static int
    send_char (ser * d, char c)
    {
    if (d->fd == -1)
    longjmp (d->env, 2);
    ssize_t nbytes = TEMP_FAILURE_RETRY (write (d->fd, &c, 1));
    return !(nbytes == 1);
    }

    I think the TEMP_FAILURE_RETRY may be important.
    but there's about a zillion other flags and iocls wot could be at fault.
    So I dont' really know if it helps.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From pozz@21:1/5 to All on Thu May 27 10:56:55 2021
    Il 26/05/2021 23:52, Johann Klammer ha scritto:
    On 05/26/2021 01:53 PM, pozz wrote:
    I used a simple driver for serial port in Linux with success in many projects, but recently I found an issue with it.

    The scenario is an embedded Linux (running on iMX6) that runs a QT application (that creates a GUI on a touch display) and a C application that communicates over a serial port.

    When QT application starts some complex graphics (I see its CPU load reaching 70-80%), the serial port application stops working correctly.
    After some debugging, I noticed that the bytes really transmitted on the wire aren't correct. It seems one or two bytes are re-transmitted and one-two bytes aren't completely transmitted.

    I suspect my serial port driver has some errors that are triggered only when the CPU is loaded by other processes, but I don't know how to fix them.

    I use O_NONBLOCK flag when opening serial port, but write() always returns a positive number, so I think the byte is correctly moved to the low-level serial port driver and really transmitted... but it isn't always the case.

    If I enable debugging messagin (with DEBUG_SERIAL macro), I always see correct data on stdout, but wrong data on the wire.

    Any hint?



    [I didn't read your code]
    But here's what I used for transmitting.

    static int
    send_char (ser * d, char c)
    {
    if (d->fd == -1)
    longjmp (d->env, 2);
    ssize_t nbytes = TEMP_FAILURE_RETRY (write (d->fd, &c, 1));
    return !(nbytes == 1);
    }

    I think the TEMP_FAILURE_RETRY may be important.
    but there's about a zillion other flags and iocls wot could be at fault.
    So I dont' really know if it helps.


    I don't think TEMP_FAILURE_RETRY could help in my case. It simply retry
    forever the write() while it returns -1 and errno==EINTR.

    My function for writing is:

    int
    serial_putchar(SERIAL_HANDLE hSerial, unsigned char c)
    {
    int bytes_written;

    bytes_written = write(hSerial, &c, 1);
    if (bytes_written != 1) return -1;

    return 0;
    }

    If write() returned -1, serial_putchar() would have returned -1 and the
    caller would have detected this situation.

    However in my case, when the problem arises, write() and read() never
    return a negative number.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)