Line Speed Control
cfgetospeed / cfsetospeed
<termios.h>
What is Terminal Line Speed?
In serial communication, line speed (also called baud rate) defines how fast data is transferred between the computer and a terminal or serial device — measured in bits per second (bps).
Think of a water pipe: the line speed is how fast water flows through it. If the terminal expects 9600 bps and your program sends at 115200 bps, communication breaks down — just like too much water in a narrow pipe.
Linux uses the termios structure to store and manage all terminal settings, including line speed. You read and write the speed using the functions cfgetospeed(), cfgetispeed(), cfsetospeed(), and cfsetispeed().
Line speed is stored using the speed_t data type. You never assign a raw number like 9600 directly — instead, you use symbolic constants defined in <termios.h>.
Why symbolic constants? Because terminals are built to support only specific standardized speeds. These speeds come from dividing a base clock rate (like 115,200 Hz on PCs) by whole numbers. For example: 115200 ÷ 12 = 9600, 115200 ÷ 3 = 38400, and so on.
| Constant | Speed (bps) | Common Use Case |
|---|---|---|
B300 |
300 | Very old modems |
B2400 |
2400 | Slow serial devices |
B9600 |
9600 | Classic default for GPS, modems |
B19200 |
19200 | Faster serial devices |
B38400 |
38400 | Moderately fast serial |
B115200 |
115200 | Modern embedded boards (UART) |
Important: On Linux, line speed is stored inside the c_cflag field of the termios structure using the CBAUD mask and CBAUDEX flag. The standard deliberately does not say exactly where speed is stored — to keep it portable.
The general pattern for changing any terminal setting (including speed) in Linux is always this 3-step flow:
tcgetattr()
Read current settings
into termios struct
cfsetospeed()
Modify the speed
field in struct
tcsetattr()
Write modified struct
back to driver
You never call cfsetospeed() alone and expect the kernel to know — you always apply it back with tcsetattr().
| Function | Purpose | Returns |
|---|---|---|
cfgetospeed(const struct termios *tp) |
Get current output speed | speed_t value |
cfgetispeed(const struct termios *tp) |
Get current input speed | speed_t value |
cfsetospeed(struct termios *tp, speed_t speed) |
Set output speed in struct | 0 on success, -1 on error |
cfsetispeed(struct termios *tp, speed_t speed) |
Set input speed in struct | 0 on success, -1 on error |
This example opens a serial port, reads the current output speed, prints it, then changes it to 38400 bps.
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
/* Helper to convert speed_t constant to human-readable bps string */
const char *speed_to_str(speed_t s)
{
switch (s) {
case B300: return "300";
case B2400: return "2400";
case B9600: return "9600";
case B19200: return "19200";
case B38400: return "38400";
case B115200: return "115200";
default: return "unknown";
}
}
int main(void)
{
int fd;
struct termios tp;
speed_t rate;
/* Open the serial port in read-write, no controlling terminal */
fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY);
if (fd == -1) {
perror("open /dev/ttyS0");
exit(EXIT_FAILURE);
}
/* Step 1: Read current terminal settings into tp */
if (tcgetattr(fd, &tp) == -1) {
perror("tcgetattr");
exit(EXIT_FAILURE);
}
/* Step 2a: Get and print current output speed */
rate = cfgetospeed(&tp);
printf("Current output speed: %s bps\n", speed_to_str(rate));
/* Step 2b: Set new output speed in tp (not applied yet) */
if (cfsetospeed(&tp, B38400) == -1) {
perror("cfsetospeed");
exit(EXIT_FAILURE);
}
/* Also set input speed to match (good practice) */
if (cfsetispeed(&tp, B38400) == -1) {
perror("cfsetispeed");
exit(EXIT_FAILURE);
}
/* Step 3: Apply the modified termios back to the driver.
* TCSAFLUSH: wait for output to drain, discard pending input */
if (tcsetattr(fd, TCSAFLUSH, &tp) == -1) {
perror("tcsetattr");
exit(EXIT_FAILURE);
}
/* Verify: read back the speed after applying */
if (tcgetattr(fd, &tp) == -1) {
perror("tcgetattr (verify)");
exit(EXIT_FAILURE);
}
rate = cfgetospeed(&tp);
printf("New output speed: %s bps\n", speed_to_str(rate));
close(fd);
return 0;
}
How to compile and test:
gcc -o line_speed line_speed.c
# Run as root or with appropriate permissions on a real serial port
sudo ./line_speed
Note on tcsetattr flags:
| Flag | Meaning |
|---|---|
TCSANOW |
Apply change immediately |
TCSADRAIN |
Apply after all output has been sent |
TCSAFLUSH |
Wait for output to drain AND discard pending input |
If you call cfsetispeed(&tp, 0) (speed = 0), it means: “when tcsetattr() is called, set the input speed to whatever the output speed is at that moment.”
This is a portable trick for keeping input and output speeds in sync on systems that maintain them as two separate values. On Linux, both speeds actually share the same internal field, so in practice they are always equal anyway.
/* Portable way to keep input speed = output speed */
cfsetospeed(&tp, B115200);
cfsetispeed(&tp, 0); /* 0 means: match the output speed */
tcsetattr(fd, TCSAFLUSH, &tp);
Q1. What does the speed_t data type represent in Linux terminal programming?
A: It is the data type used to hold a terminal line speed (baud rate). It stores symbolic constants like B9600 or B115200, not raw numeric values directly. It is defined in <termios.h>.
Q2. Why does Linux use symbolic constants like B9600 instead of the number 9600?
A: Terminals only support a limited set of standardized speeds derived by dividing a base clock rate by whole numbers. The actual encoding of the speed inside the termios structure is implementation-specific. Using symbolic constants keeps the code portable across different UNIX systems, regardless of how the speed is internally encoded.
Q3. What is the correct sequence to change the baud rate of a serial terminal in Linux?
A: (1) Call tcgetattr(fd, &tp) to read the current settings. (2) Call cfsetospeed(&tp, B38400) (and optionally cfsetispeed()) to modify the speed in the struct. (3) Call tcsetattr(fd, TCSAFLUSH, &tp) to apply the change to the driver.
Q4. Where does Linux store the terminal line speed inside the termios structure?
A: Linux stores the line speed in the c_cflag field using the CBAUD mask and CBAUDEX flag. SUSv3 deliberately leaves the storage location unspecified for portability. The c_ispeed and c_ospeed fields that appear in the Linux termios structure are actually unused.
Q5. What happens when you pass 0 as the speed to cfsetispeed()?
A: It means “set the input speed equal to the output speed when tcsetattr() is applied.” It is a portable way to synchronize input and output speeds on systems where they can differ. On Linux, both speeds share the same field anyway.
Q6. What is the difference between TCSANOW, TCSADRAIN, and TCSAFLUSH in tcsetattr()?
A: TCSANOW applies the change immediately. TCSADRAIN waits until all pending output has been transmitted before applying. TCSAFLUSH waits for output to drain and also discards any pending unread input. For baud rate changes, TCSAFLUSH is the safest choice.
Q7. Can input and output line speeds be different in Linux?
A: Theoretically, cfsetispeed() and cfsetospeed() allow setting them separately. But on Linux, both speeds share the same internal field, so they are always equal. On many real hardware terminals also, both speeds must match for correct communication.
Next: Terminal Line Control — tcdrain, tcflush, tcflow, tcsendbreak
