mirror of
https://github.com/UzixLS/picocom.git
synced 2025-07-19 15:31:24 +03:00
175 lines
7.5 KiB
Plaintext
175 lines
7.5 KiB
Plaintext
|
|
Linux and custom serial-port baudrates (the gory details)
|
|
=========================================================
|
|
|
|
Support for custom baudrate setting and reading in Linux is done
|
|
through the "new" "termios2" terminal-attributes structure, and the
|
|
respective ioctls: TCSETS2, TCSETSW2, TCSETSF2, and TCGETS2.
|
|
|
|
The "termios2" structure is defined in:
|
|
|
|
<linux-kernel>/arch/<arch>/include/uapi/asm/termbits.h
|
|
or <linux-kernel>/include/uapi/asm-generic/termbits.h
|
|
|
|
which may have been coppied in your system headers directories as:
|
|
|
|
/usr/include/<arch>/asm/termbits.h
|
|
or /usr/include/asm-generic/termbits.h
|
|
|
|
The termios2 structure looks like this:
|
|
|
|
#define NCCS 19
|
|
struct termios2 {
|
|
tcflag_t c_iflag; /* input mode flags */
|
|
tcflag_t c_oflag; /* output mode flags */
|
|
tcflag_t c_cflag; /* control mode flags */
|
|
tcflag_t c_lflag; /* local mode flags */
|
|
cc_t c_line; /* line discipline */
|
|
cc_t c_cc[NCCS]; /* control characters */
|
|
speed_t c_ispeed; /* input speed */
|
|
speed_t c_ospeed; /* output speed */
|
|
};
|
|
|
|
In the same files you will also find defined some relevant macros
|
|
(constants, flags, and bit-fields, for the "termios2" "c_*flag"
|
|
fields).
|
|
|
|
Important aside: Unfortunatelly, we cannot include the above-mentioned
|
|
files in our code, since they clash badly with stuff defined in the
|
|
LIBC-provided <termios.h> header files (where the user-visible
|
|
"termios" structure is defined). Because of this clash between LIBC
|
|
and the linux headers, we have to manually copy the "termios2"
|
|
definition (and a few relevant constants) into our sources for the
|
|
whole thing to work. This is definitely very klugy, but I can see no
|
|
better way to make it work (after all, GLIBC does the same
|
|
thing---replicates the kernel definitions itself---for the older
|
|
"termios" interface).
|
|
|
|
End aside, on with it...
|
|
|
|
The new ioctls TCSETS2, TCSETSW2, TCSETSF2 pass a "termios2" structure
|
|
to the kernel in order to set the tty attributes (among which the
|
|
serial port's baudrate). The corresponding TCGETS2 ioctl retrieves a
|
|
"termios2" structure from the kernel corresponding to the actual,
|
|
effective tty settings.
|
|
|
|
These ioctls are used by the "tc2setattr()" and "tc2getattr()"
|
|
functions (see file "termios2.c"). These functions are passed a
|
|
userspace, LIBC-defined "termios" structure (which is very similar,
|
|
but not necessarily identical to the kernel's "termios2"), call the
|
|
respective ioctls, and copy the relevant information to or from the
|
|
"termios2" structure (as expected or returned by the kernel).
|
|
|
|
The game between the kernel and the "termios2" structure, regarding
|
|
how baudrate-related information is interpretted, is played like this:
|
|
|
|
Bits "c_cflags & (CBAUD | CBAUDEX)" (they are sometimes called: "the
|
|
CBAUD field"), together with field "c_ospeed" control the output
|
|
baudrate.
|
|
|
|
Bits "c_cflags & ((CBAUD | CBAUDEX) << IBSHIFT)" (they are sometimes
|
|
called: "the CIBAUD field"), together with field "c_ispeed" control
|
|
the input baudrate.
|
|
|
|
BTW: Usually CBAUD & CBAUDEX == CBAUD. That is, CBAUDEX *is*
|
|
one of the CBAUD bits. See end of this file for more on the usual
|
|
relationship of CBAUD, CBAUDEX, BOTHER
|
|
|
|
When issuing one of the TCSETS*2 ioctls, everything contained in the
|
|
"termios2" structure is copied to a kernel-resident structure and the
|
|
respective serial driver is notified. Upon the serial driver's
|
|
request, the kernel determines the output baudrate. If the kernel sees
|
|
that:
|
|
|
|
c_cflag & CBAUD == BOTHER
|
|
|
|
then "c_ospeed" is passed to the serial driver as the output
|
|
baudrate. Otherwise "c_cflag & (CBAUD | CBAUDEX)" is matched against a
|
|
table of standard baudrates (coresponding to the "Bxxxx" macros). The
|
|
matching baudrate is located and passed to the serial driver as the
|
|
output baudrate.
|
|
|
|
You can see the respective code in "drivers/tty/tty_ioctl.c" (all
|
|
files form now on relative to the linux-kernel source tree base),
|
|
function "tty_termios_baud_rate()", which is what the serial drivers
|
|
call to determine the output baudrate.
|
|
|
|
If the driver requests it, the kernel also determines the input
|
|
baudrate by checking (c_cflag >> IBSHIFT) & CBAUD. If it sees that:
|
|
|
|
(c_cflag >> IBSHIFT) & CBAUD == B0
|
|
|
|
then the *output* baudrate is passed to the driver (as the input
|
|
baudrate) determined as described above. If, on the other hand the
|
|
kernel sees that:
|
|
|
|
(c_cflag >> IBSHIFT) & CBAUD == BOTHER
|
|
|
|
then "c_ispeed" is passed to the serial driver as the input
|
|
baudrate. If, finaly, neither is true (i.e. the input baudate bits in
|
|
"c_cflag" are neither B0, nor BOTHER), then "(c_cflag >> IBSHIFT) &
|
|
(CBAUD|CBAUDEX)" is mached against the table of standard baudrates.
|
|
The matching baudrate is located and passed to the serial driver as
|
|
the input baudrate.
|
|
|
|
You can see all these happen in "drivers/tty/tty_ioctl.c", function
|
|
"tty_termios_input_baud_rate()", which is what the serial drivers call
|
|
to determine the input baudrate.
|
|
|
|
The serial driver, once it receives the requested baudrate values, it
|
|
may choose to alter them (e.g because the requested values are not
|
|
supported by the hardware). If it does so, it then passes-back to the
|
|
kernel the actual, effective, baudrate values, so that the kernel can
|
|
update its internal structure. As a result, the user will read the
|
|
*effective* baudrate values with the next TCGETS2 ioctl (which may be
|
|
different than the requested ones).
|
|
|
|
The kernel updates the "termios2" structure with the effective
|
|
baudrate values supplied by the serial driver by following a rather
|
|
complicated procedure, which I will not describe in full detail
|
|
here. The gist of it is this: If the user has requested a baudrate
|
|
using one of the standard "Bxxx" values (i.e. by setting the CBAUD /
|
|
CIBAUD fields in "c_cflag"), then the kernel will also try to
|
|
report-back the effective baudrate as a standard "Bxxx" value (by
|
|
setting the CBAUF / CIBAUD fields in "c_cflag"), *even* if it has to
|
|
lie a little about the baudrate value. If lying "a little" is not
|
|
enough, or if the user has requested a non-standard baudrate through
|
|
"c_ispeed / c_ospeed", then the kernel will set the CBAUD / CIBAUD
|
|
fields in "c_cflag" to BOTHER, and report the effective baudrate
|
|
(numerically) using "c_ispeed" / "c_ospeed". Actually, the "c_ispeed"
|
|
/ "c_ospeed" fields are *always* updated by the kernel with the
|
|
effective baudrate values, even if these values are also reported by
|
|
setting the CBAUD / CIBAUD fields in "c_cflag" to one of the "Bxxx"
|
|
values.
|
|
|
|
The details of how the kernel updates the termios2 structure with the
|
|
baudrate values supplied by the serial drivers can be seen in file
|
|
"drivers/tty/tty_ioctl.c", or "drivers/tty/tty_baudrate.c" function
|
|
"tty_termios_encode_baud_rate()" which is what the serial drivers call
|
|
to notify the kernel about the effective baudrate.
|
|
|
|
|
|
Baud-codes, CBAUD, CBAUDEX, and BOTHER
|
|
--------------------------------------
|
|
|
|
CBAUD = 0010017 ---> 0001 0000 0000 1111 mask
|
|
CBUADEX = 0010000 ---> 0001 0000 0000 0000 extender bit
|
|
BOTHER = 0010000 ---> 0001 0000 0000 0000 a reserved code
|
|
|
|
Usually CBAUD includes CBAUDEX. In any case the mask is always CBAUD |
|
|
CBAUDEX (which is usually == CBAUD)
|
|
|
|
Basic codes (Bxxx & CBAUDEX == 0)
|
|
|
|
B0 > ---0 ---- ---- 0000
|
|
B50 > ---0 ---- ---- 0001
|
|
... 16 in total ...
|
|
B38400 > ---0 ---- ---- 1111
|
|
|
|
Extended codes (Bxxx & CBAUDEX == 1)
|
|
|
|
CBAUDEX / BOTHER > ---1 ---- ---- 0000
|
|
B57600 > ---1 ---- ---- 0001
|
|
... 16 in total ...
|
|
B4000000 > ---1 ---- ---- 1111
|