0

I am using USB to RS-232 serial adapters and cannot set the line properties to use custom baud rates on linux (fedora 26 or fedora 32) using:

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <termio.h>
#include <linux/serial.h>
#include <err.h>

#include "portutils.h"

static int rate_to_constant(int baudrate) {
#define B(x) case x: return B##x
        switch(baudrate) {
        B(50);     B(75);     B(110);    B(134);    B(150);
        B(200);    B(300);    B(600);    B(1200);   B(1800);
        B(2400);   B(4800);   B(9600);   B(19200);  B(38400);
        B(57600);  B(115200); B(230400); B(460800); B(500000);
        B(576000); B(921600); B(1000000);B(1152000);B(1500000);
    default: return 0;
    }
#undef B
}

int main(int argc, char** argv) {

    struct termios options;
    struct serial_struct serinfo;
    int fd;
    int speed = 0;
    int baudrate = 625000;
    char * port_name;

      // Check arguments for correct usage
    if (argc != 3){
        printf("\n  Usage: %s port baudrate!\n\n", argv[0]);
        return -1;
    }

    print_bar();

    baudrate = atoi(argv[2]);
    port_name = argv[1];

    /* Open and configure serial port */
    if ((fd = open(port_name, O_RDWR|O_NOCTTY)) == -1)
    {
        printf("\n  Could not open port %s\n", port_name);
        goto HELL;
    }

    printf("  Trying to set baud rate to %d\n  On port %s\n", baudrate, port_name);

    // if you've entered a standard baud the function below will return it
    speed = rate_to_constant(baudrate);

    if (speed == 0) {
        /* Custom divisor */
        serinfo.reserved_char[0] = 0;
        if (ioctl(fd, TIOCGSERIAL, &serinfo) < 0) {
            printf("\n  ioctl: Failed to get #1\n  serial line information of %s\n", port_name);
            goto HELL;
        }
        serinfo.flags &= ~ASYNC_SPD_MASK;
        serinfo.flags |= ASYNC_SPD_CUST;
        serinfo.custom_divisor = (serinfo.baud_base + (baudrate / 2)) / baudrate;
        if (serinfo.custom_divisor < 1) {
            serinfo.custom_divisor = 1;
        }
        printf("  Baud_base: %d\n", serinfo.baud_base);
        printf("  Devisor: %d\n", serinfo.custom_divisor);
        printf("  Resulting baudrate: %f\n", serinfo.baud_base/(1.0*serinfo.custom_divisor));        
        if (ioctl(fd, TIOCSSERIAL, &serinfo) < 0) {
            printf("\n  ioctl: Failed to set\n  serial line information of %s\n", port_name);
            goto HELL;

        }
        if (ioctl(fd, TIOCGSERIAL, &serinfo) < 0) {
            printf("\n  ioctl: Failed to get #2\n  serial line information of %s\n", port_name);
            goto HELL;
        }
        if (serinfo.custom_divisor * baudrate != serinfo.baud_base) {
            warnx("actual baudrate is %d / %d = %f",
                  serinfo.baud_base, serinfo.custom_divisor,
                  (float)serinfo.baud_base / serinfo.custom_divisor);
        }
    }

    fcntl(fd, F_SETFL, 0);
    tcgetattr(fd, &options);
    cfsetispeed(&options, speed ?: B38400);
    cfsetospeed(&options, speed ?: B38400);
    cfmakeraw(&options);
    options.c_cflag |= (CLOCAL | CREAD);
    options.c_cflag &= ~CRTSCTS;
    if (tcsetattr(fd, TCSANOW, &options) != 0)
    {
        printf("\n  Failed to set final attributes of %s\n\n", port_name);
        goto HELL;
    }

    close(fd);
    printf("  Success on port %s\n", port_name);
    print_bar();

    return 0;

HELL:
 
    print_bar();
    return -1;
}

I am working with an adapter using an ASIX chip set and one using an FTDI chip set, the FTDI based device has no problems but the other simply returns -1 from ioctl when i try to set it up with the first TIOCSSERIAL command. So is there a way to detect if the TIOCSSERIAL command not is supported by the used driver?

PS! I am using the label HELL: as a common return point on error of my test program ;-)

The Schwartz
  • 757
  • 1
  • 8
  • 13
  • TIOC*SERIAL ioctls itself are implemented in serial core (therefore supported by all drivers), but the specific options or flags, which you set, may not be supported by all drivers. See if `dmesg` says something. – Erki Aring Sep 20 '20 at 20:34
  • Thanks @ErkiAring all dmesg gives is: [ 4.684266] usb 3-1: FTDI USB Serial Device converter now attached to ttyUSB0 [ 4.684524] usb 3-2: FTDI USB Serial Device converter now attached to ttyUSB1 [ 4.687079] usb 1-1.2: Moschip 7840/7820 USB Serial Driver converter now attached to ttyUSB2 [ 4.689288] usb 1-1.2: Moschip 7840/7820 USB Serial Driver converter now attached to ttyUSB3 – The Schwartz Sep 21 '20 at 09:07
  • *"the other simply returns -1 from ioctl"* -- Read the **man** page(s). Typically when a syscall returns `-1`, then the global variable **errno** needs to be fetched for details. *"How to detect if a driver (not) supports the ioctl command"* -- Typically there is no syscall to inquire about another syscall or ioctl capability. Instead simply make the request, and check if it succeeds or is rejected (e.g. return code is `-1` and **errno** is `ENOIOCTLCMD`). – sawdust Sep 21 '20 at 23:03

1 Answers1

1

USB-to-serial adapters do not support nor need those setserial ioctls.

If you want to set a custom speed on a USB-to-serial adapter, you should use the new TCSETS2, TCSETSW2 and TCSETSF2 ioctls, which take a struct termios2 where you should set the BOTHER flag in .c_cflag and use the .c_ispeed and .c_ospeed fields directly. Look at /usr/include/asm-generic/termbits.h.

No divisor setting or other such thing is necessary.

The last time I did that I had to use some #define kludges in order to be able to include those headers (they conflict with the termios headers from glibc).

Example wrappers (from some old sources, UNTESTED):

#include <sys/ioctl.h>
#define termios termios_HIDE
#define termio termio_HIDE
#define winsize winsize_HIDE
#include <asm/termios.h>
#undef termios
#undef termio
#undef winsize
#define termios termios2
#define tcsetattr tcsetattr2
#define tcgetattr tcgetattr2

int tcsetattr2(int fd, int act, struct termios *ts){
        return act == TCSANOW ? ioctl(fd, TCSETS2, ts) :
                act == TCSADRAIN ? ioctl(fd, TCSETSW2, ts) :
                ioctl(fd, TCSETSF2, ts);
}
int tcgetattr2(int fd, struct termios *ts){
        return ioctl(fd, TCGETS2, ts);
}
int cfsetspeed(struct termios *ts, speed_t s){
        ts->c_cflag |= BOTHER;
        ts->c_ispeed = ts->c_ospeed = s;
        return 0;
}