The other day, I was working on a TI AM3517-based Linux device and I needed to open a serial port at a weird baud rate (31250 bps). The AM3517 is one of TI’s Sitara processors, which are very similar to the popular OMAP chips. The AM3517 is definitely physically capable of doing this baud rate — it starts out with a 48 MHz clock, then it oversamples by either 16X or 13X (thus giving you a 3 MHz or 3.692308 MHz base clock), and then you can supply an integer divisor at that point. With the 16X oversampling, a divisor of 96 would give you 48,000,000 / 16 / 96 = 31250 baud. There was no question that the AM3517 could do the job.

Although the hardware supports it, Linux made it difficult to get the job done. Well, I guess it’s more accurate to say that the combination of Linux and the C library made it difficult.

First of all, the standard way of setting the serial speed is to do the following:

struct termios tio;
tcgetattr(fd, &tio);
cfsetispeed(&tio, B115200);
cfsetospeed(&tio, B115200);
/* do other miscellaneous setup options with the flags here */
tcsetattr(fd, TCSANOW, &tio);

That’s great, except only some standard baud rates have constants: B9600, B19200, B38400, B57600, B115200, etc. There’s no B31250 constant, and you can’t just pass a number to cfsetispeed() and cfsetospeed(). It doesn’t work that way because the speeds are really combinations of bits that get set in the c_cflag member of the termios struct. So essentially, the standard POSIX tcsetattr/tcgetattr API is kind of lame.

I already knew there were ways to get around that problem. I started out by trying the old method that setserial uses:

struct serial_struct serialsettings;
ioctl(fd, TIOCGSERIAL, &serialsettings);
serialsettings.flags &= ~ASYNC_SPD_MASK;
serialsettings.flags |= ASYNC_SPD_CUST;
serialsettings.custom_divisor = 96;
ioctl(fd, TIOCSSERIAL, &serialsettings);

After running that, opening the port with a baud rate of 38400 is supposed to actually use the custom divisor. It’s an ugly way to do it, but it works on a Linux 2.4-based device that I’ve used in the past.

It compiled just fine, but first of all, as soon as my program tried to do the above code, the kernel (the device I’m using has Linux 2.6.37) warned me that setting a custom divisor is deprecated. Worse yet, the TIOCSSERIAL ioctl failed with an error of EINVAL. This is because the kernel’s OMAP serial driver (drivers/serial/omap-serial.c, or drivers/tty/serial/omap-serial.c on newer kernels) doesn’t support that call: serial_omap_verify_port() function returns -EINVAL. So deprecated or not, it just plain doesn’t work with the OMAP serial driver.

I did some looking and dug up a discussion about a replacement for the old, deprecated TIOCSSERIAL method. I also found some other promising leads, including a post on StackOverflow asking the same question.

The discussion about a replacement comes to a conclusion: if the CBAUDEX bit in the c_cflag member of the termios structure is set without any of the other bits from the other B#### constants, then you can put an integer value for the desired baud rate into the c_ispeed and c_ospeed members in the struct. Then, I found a draft patch for this change. The patch includes a new constant called BOTHER (that’s B-OTHER, not “bother!”) defined as the same thing as CBAUDEX by itself.

The patch ended up changing–nowadays, you’ll find that in the kernel, there’s a struct termios and then a struct termios2. termios2 is the new struct that implements the new speed fields. I looked in my glibc header file bits/termios.h, and glibc’s header file actually includes the c_ispeed and c_ospeed members in the standard termios struct. However, the actual code that talks to the Linux kernel, at least in my case, was copying the termios data into an old-style termios struct and using the old-style ioctl that doesn’t look at c_ispeed and c_ospeed.

So after a ton of messing around, I figured out what it takes to do a new-style termios call to set a custom baud rate on newer (2.6+) versions of Linux:

#include <asm/termios.h>

struct termios2 tio;
ioctl(fd, TCGETS2, &tio);
tio.c_cflag &= ~CBAUD;
tio.c_cflag |= BOTHER;
tio.c_ispeed = 31250;
tio.c_ospeed = 31250;
/* do other miscellaneous setup options with the flags here */
ioctl(fd, TCSETS2, &tio);

After doing that, everything worked fine and the OMAP UART worked perfectly at 31250 baud, confirmed with an oscilloscope.

I ran into some #include hell with asm/termios.h, especially if I already had the normal termios.h included elsewhere, so you might have to copy relevant struct definitions into your own code if you can’t get it to work (I know, nasty!). The other thing to keep in mind is that the definition of NCCS might be different between your libc and kernel, so you need to make sure you’re using the kernel’s definition of NCCS. I believe asm/termbits.h has the correct NCCS and structures defined.

Make sure you don’t do another normal tcsetattr() call after the TCSETS2 ioctl, because I’m guessing it’ll probably revert the custom baud rate back to a standard baud rate if you do. I’m not 100% sure on that though.

Anyway, I hope this helps someone. In the end, it wasn’t really that complicated to get working, but it took a lot of research and playing around to understand how to make it work. Maybe I’m missing something, but it seems like glibc and the kernel don’t play well together in this case. It’s also possible that glibc supports this all perfectly and my glibc is compiled incorrectly or something, but either way, manually doing the new-style ioctl like I did above seems to work.

Trackback

1 comment

  1. Setup random baud rate in Ubuntu 14.04 topic | ubuntu @ 2015-07-10 02:17

Add your comment now