tty layer (2.4)
This section focusses on the TTY layer.
This is a messy thing because, for historical reasons,
it deals with serial lines, modems, printers, and other hardware.
[some description]
The user perspective
References:
W.R. Stevens, "Adavnced Programming in the UNIX environment",
Addison-Wesley 1993
The user perspective of the tty layer is the terminal I/O.
This is a complex topic because it is used to control a lot of different
devices (terminals, modem, serial lines, ...).
The logical view is shown in the figure on the side.
Between the user input/output, mediated by the device drivers,
and the process read/write, there is the tty layer, which buffers
the data in two queues. Both queues have a maximum size.
When the output queue is full the process is put to wait.
When the input queue is full characters are lost.
If echo is enabled the input characters are echoed on the output queue.
The kernel characteristics of a tty are contained in a
struct termio
(defined in include/asm/termios.h). This contains
c_iflag
, the input mode flags;
c_oflag
, the output mode flags;
c_lflag
, the local mode flags;
c_cflag
, the control mode flags;
c_line
, line discipline type (this is not seen by the
user);
c_cc
, an array of control characters
A similar structure exists (
termios
) which differ only in
the typing of the fields (POSIX like).
Userland has the
struct termios
(define in
/usr/include/bits/termios.h) which contains also i/o speed fields.
The values of the flags bits and the special characters
are listed in the tables below.
c_iflag |
BRKINT |
generate SIGINT on BREAK |
ICRNL |
map CR to NL on input |
IGNBRK |
ignore BREAK |
IGNCR |
ignore CR |
IGNPAR |
ignore characters with parity errors |
IMAXBEL |
ring bell on input when the input queue is full |
INLCR |
map NL to CR |
INPCK |
enable input parity checking |
ISTRIP |
strip eighth bit off input characters |
IUCLC |
map uppercase to lowercase on input |
IXANY |
exable any character to restart output |
IXOFF |
enable start/stop input flow control |
IXON |
enable start/stop output flow control |
PARMRK |
mark parity errors |
c_oflag |
BSDLY |
backspace delay mask |
CRDLY |
CR delay mask |
FFDLY |
formfeed delay mask |
NLDLY |
NL delay mask |
OCRNL |
map CR to NL on output |
OFDEL |
fill is DEL (else is NUL) |
OFILL |
use fill characters for delay |
OLCUL |
map lowercase to uppercase on output |
ONLCR |
map NL to CR-NL (this is CRMOD) |
ONLRET |
NL performs CR function |
ONOCR |
no CR output at column 0 |
ONOEOT |
discard EOF (^D) on output |
OPOST |
perform output processing |
OXTABS |
expand tabs to spaces |
TABDLY |
horizontal tab delay mask |
VTDLY |
vertical tab delay mask |
c_cflag |
CCTS_OFLOW |
CTS flow control of output |
CIGNORE |
ignore control flags |
CLOCAL |
ignore modem status line |
CREAD |
enable receiver |
CRTS_IFLOW |
RTS flow control on input |
CSIZE |
character size mask |
CSTOPB |
send two stop bits (else send one) |
HUPCL |
hangup on last close |
MDMBUF |
flow control output via Carrier |
PARENB |
parity enable |
PARODD |
parity is odd (else is even) |
c_lflag |
ALTWERASE |
use alternate WERASE algorithm |
ECHO |
enable echo |
ECHOCTL |
echo control characters as ^Char |
ECHOE |
visually erase characters |
ECHOK |
echo KILL (by erasing the entire line) |
ECHOKE |
visual erase for KILL |
ECHONL |
echo NL |
ECHOPRT |
visula erase mode for hardcopy |
FLUSHO |
output being flushed |
ICANON |
canonical input |
IEXTEN |
enable extended input character processing |
ISIG |
enable terminal-generated signals |
NOFLSH |
disable flush after interrupt or quit |
NOKERNINFO |
no kernel output from STATUS |
PENDIN |
retype pending input |
TOSTOP |
send SIGTTOU for background output |
XCASE |
canonical upper/lower presentation |
Special characters |
subscript |
enabled by |
CR |
carriage return |
- |
ICANON |
EOF |
end-of-file |
VEOF |
EOL |
end-of-line |
VEOL |
EOL2 |
alternate end-of-line |
VEOL2 |
ERASE |
backspace one character |
VERASE |
KILL |
erase line |
VKILL |
NL |
linefeed |
- |
REPRINT |
reprint all input |
VREPRINT |
STATUS |
status request |
VSTATUS |
WERASE |
backspace one word |
VWERASE |
START |
resume output |
VSTART |
IXON / IXOFF |
STOP |
stop output |
VSTOP |
DSUSP |
delayed suspend (SIGTSTP) |
VDSUSP |
ISIG |
INTR |
interrupt signal (SIGINT) |
VINTR |
QUIT |
quit signal (SIGQUIT) |
VQUT |
SUSP |
suspend signal (SIGTSTP) |
VSUSP |
DISCARD |
discard output |
VDISCARD |
IEXTEN |
LNEXT |
literal next |
VLNEXT |
The user control of the tty can be done with the
ioctl()
function, but its last argument depends on the action being performed.
This makes type checking impossible.
Therefore POSIX defines 12 functions,
tcsetattr
, tcgetattr
: to
set/get the tty attributes;
cfgetispeed
, cfsetispeed
,
cfgetospeed
, cfsetospeed
: for the i/o
speeds (baud rate);
tcdrain
, tcflow
, tcflush
and tcsendbreak
are line control functions;
tcgetpgrp
and tcsetpgrp
: get/set
foreground process group ID.
Canonical mode
Canonical mode (cooked) is simple, from the user point of view:
reads are linewise, ie, the read() call returns when a complete line
has been entered by the device driver.
A line is complete when one of the following delimiters is entered:
NL, EOL, EOL2, EOF (and CR if ICRNL is set and IGNCR is not set).
Note that EOF is not returned with the read.
Noncanonical mode
This is set by turning off ICANON.
The input characters are not assembled into lines.
These special characters are not processed: ERASE, KILL, EOF, NL, EOL, EOL2,
CR, REPRINT, STATUS, WERASE.
The two entries
c_cc[VMIN]
and
c_cc[VTIME]
determine the behaviour of the
read()
.
There are four cases:
MIN=0 | TIME=0 |
read() returns immediately with whatever data are available
(from 0 if none, to the maximum number requested in the call) |
MIN=0 | TIME>0 |
read() returns between 1 and the requested number if there are
data before the time expires; it returns 0 if the time expires |
MIN>0 | TIME=0 |
read() returns between 1 and the requested number when there are
data available (may wait forever) |
MIN>0 | TIME>0 |
read() returns between MIN and the number requested before the
time expires, or or between 1 and MIN-1 if the time expires.
It can block forever if there are no data |
There are two noncanonical modes (i am aware of), cbreak and raw.
cbreak |
ECHO and ICANON off |
|
|
|
MIN=1, TIME=0 |
raw |
ECHO, ICANON, IEXTEN and ISIG off |
BRKINT, ICRNL, INPCK, ISTRIP, IXON off |
CSIZE, PARENB and OPOST off |
CS8 (set 8-bit characters) |
MIN=1, TIME=0 |
To conclude this summary of terminal I/O
look at this example from Steven's book.
tty_raw.c, set a tty in raw mode
tty kernel structures
References:
G. Kroah-Hartman, "The tty layer", Linux Journal, Aug. 2002, 40-47
G. Kroah-Hartman, "The tty layer - Part II", Linux Journal, Oct. 2002,
50-58
G. Kroah-Hartman, "The USB serial driver layer",
Linux Journal, Feb. 2003, 38-41
G. Kroah-Hartman, "The USB serial driver layer - Part II"
Linux Journal, Apr. 2003, 32-35
The tty layer is split into two structures: the tty driver and the line
discipline. The driver handles the backend hardware; the line discipline
is like a filter on the input/output.
These two structures are contained in the
tty_struct
, which has
also a structure, called flip buffer, for buffering input characters, and
pointers to the termios.
These structures are defined in four header files, in the
include/linux subdirectory,
tty.h
, with screen_info
,
tty_flip_buffer
and
tty_struct
;
tty_driver.h
, with tty_driver
;
tty_ldisc.h
, with tty_ldisc
;
tty_flip.h
, defines two inline routines
tty_insert_flip_char
: insert a char and a flag into the
flip
buffers;
tty_schedule_flip
: append the flip tqueue
to the timer task queue. The routine tty_flip_buffer_push
either calls flush_to_ldisc()
, if the low-latency flag
is set, or submit the tty's flip buffer tqueue
on the timer queue. This tqueue is initialized with the
flush_to_ldisc()
routine and the tty data: see
the description of initialize_tty_struct
below.
The screen_info
contains video informations, and data
for the linear frame buffer of VESA graphic mode.
The tty_flip_buffer
contains two input buffers of size twice
TTY_FLIPBUF_SIZE
(512), one of characters the other of flags
(for parity errors).
The flip buffers are logically made of two parts, and the code fills
alternatively one or the other.
buf_num
denotes the part in use, and count
the number of bytes that have been received.
There is a task_queue tqueue
and a semaphore pty_sem
.
TTY, tty_struct
- It contains a tty_driver
driver
;
- a line discipline
ldisc
;
- pointers to the
termios
and termios_locked
;
- several flags and counters;
- a
link
to another tty_struct's (for ptty).
It is used in the stop
/start
methods of the
tty_driver: the link's read_wait queue is waken up if its
packet
flag is set;
- a tty_flip_buffer
flip
;
- two wait_queue_head_t,
write_wait
and read_wait
;
- a tq_struct
tq_hangup
;
- a list of
tty_files
;
- other fields for the N_TTY line discipline.
Device driver, tty_driver
- It contains accessory informations:
driver_name
, name
, major
,
num
(number of devices), type
and
subtype
(type and subtype of the tty driver),
flags
, and others
- the pointer to the
table
of tty_struct;
termios
and termios_locked
;
- two pointers for a doubly-linked list,
next
and
prev
.
- the tty_driver methods:
open
, called after a user open.2;
close
, called after a user close.2
write
, called after a user write.2 or when the kernel
needs to write to the tty device
put_char
, called by the kernel to write a single
character;
flush_chars
, called by the kernel after writing a number
of characters to the tty device using put_char.
write_room
, returns the number of characters the tty
device will accept for writing.
ioctl
, driver specific ioctl's
set_termios
, used to notify the tty driver of a change
of termios;
set_ldisc
, used to notify the tty driver of a change
of line discipline;
throttle
, tells the tty driver that the input buffers
of the line discipline are close to full;
unthrottle
, tells the tty driver that the input buffers
of the line discipline have enough space;
stop
, stops the tty driver from sending out characters
to the tty device;
start
, starts the tty driver to send out characters
to the device;
hangup
, tells the tty driver to hang up the tty device;
break_ctl
, toggle the BREAK status on the RS-232 port;
wait_until_sent
, the tty driver waits until the device
has written out all the characters in its FIFO queue;
send_xchar
, send a high-priority XON-XOFF character
to the device.
Line discipline, tty_ldisc
- A line discipline consists of a
magic
number,
a name
, flags
(of the line discipline),
num
(index of the line discipline in the global
ldiscs
array);
- It has methods called from above, ie, from the user perspective,
open
, called when the line discipline is associated
with the tty. The line discipline can do any suitable initialization
and return negative on failure.
close
, called when the line discipline is shut down, either
because the tty is closed or it changes the line discipline.
read
, called after a user read.2.
write
, called after a user write.2. It must deliver the
characters to the low-level tty device.
ioctl
, called after a user ioctl.2 that is not handled
by the tty layer or the low-level tty driver.
poll
, called after a user select/poll.
set_termios
, called after a user set_termios.
flush_buffer
, called to tell the line discipline to
send any input character in the buffer to the user process.
chars_in_buffer
, returns the number of input characters
buffered by the line discipline.
- It defines methods called from below, ie from the low-level tty_driver
receive_buf
, called to send
characters to the line discipline.
receive_room
,
returns the number of characters that the line discipline can accept.
write_wakeup
, called to tell the line discipline that
it can send down more characters.
tty_fops
tty_register_driver()
inserts the tty_driver into the list
of the
tty_drivers
, and if it does not provide its own
routine
put_char()
assignes the
tty_default_put_char()
to it.
It registers the tty_driver with the devfs (devfs_register_chrdev), and
with the proc filesystem (proc_tty_register_driver).
The file operations are
tty_fops
:
tty_read
: after some checks it locks the kernel and calls
the line discipline read function. Then it unlocks the kernel;
tty_write
: it does some checks and then calls
do_tty_write()
which down's the semaphore
atomic_write
, locks the kernel, calls the write function
(of the line discipline), and unlocks and ups;
tty_poll
calls the poll routine of the line discipline;
tty_ioctl
;
tty_open
: essentially it calls init_dev()
to retrieve the tty_struct for the device number, and the
open
routine of the tty's driver;
tty_release
calls release_dev()
;
tty_fasync
;
init_dev()
allocates a new tty_struct,
and initializes it (by calling initialize_tty_struct()
);
sets up the tty's termios, termios_locked, device, driver, count;
sets up the driver's table entry for this device;
finally it calls the tty's line discipline open()
.
initialize_tty_struct()
performs the initialization of a tty_struct.
It sets the magic number (TTY_MAGIC), the line discipline (with the
default ldiscs[N_TTY]), the fields in the tty flip_buffer (in particular
the tqueue
with routine flush_to_ldisc
and data
the tty
), the write and read wait_queues, the task_queue
tq_hangup
(routine do_tty_hangup
, data
tty
).
Finally it initializes the semaphores atomic_read
and
atomic_write
, the list_head tty_files
, and
the secure-attention-key task_queue SAK_tq
.
flush_to_ldisc()
:
if the tty has the TTY_DONT_FLIP flag set, the flip tqueue
is queued on the timer queue.
Otherwise the buffer pairs are "flipped", the flip count
is reset to zero, and the line discipline receive_buf
is called.
The line discipline tty_ldisc_N_TTY
The line discipline
tty_ldisc_N_TTY
is defined in
n_tty.c with the following methods,
n_tty_open
allocates a read_buf
for the
tty_struct;
n_tty_close
flushes the buffer and frees the
read_buf
n_tty_flush_buffer
clears everything and unthrottle the
driver;
n_tty_chars_in_buffer
returns the number of characters in the
buffer to be delivered to the user, ie, the difference between
read_head
and read_tail
read_chan
is rather complicated, but essentially it amounts to
add the current process to the tty_struct read_wait
queue;
copy characters from the buffer to the user buf.
When done remove the current process from the wait_queue.
write_chan
adds the current process to the tty_struct write_wait
queue;
calls the tty driver write(tty, 1, buf, count) routine;
possibly schedule().
When done removes the current process from the wait_queue.
n_tty_ioctl
n_tty_settermios
normal_poll
n_tty_receive_buf
copies the received chars in the
read_buf
with possible line discipline processing.
If the tty_struct is not ICANON and the read_cnt
is more
than the minimum_to_wake
calls the fasync method on the
tty_struct. Finally it checks whether to throttle or not the device.
n_tty_receive_room
n_tty_write_wakeup
calls the fasync method on the tty_struct
Exercise
The point of this exercise is to write a memory-based tty_driver.
The hardware output should be provided by a memory buffer.
Therefore the driver write() function that sends the user data on the
output port should copy the data on this buffer.
A timer can be used to emulate a delay in the transmission of the
data. In a real situation there would be an interrupt raised by the
hardware when data arrive, and the timer tasklet would push the data
to the line disipline, so that they can be delivered to the user
read() function.
[more description of the example]
You can now experiment changing the device settings and
observing how changes the call to the read/write functions.
For example, by turning ECHO on, the line discipline will
echo all the characters from the flip buffer to the write()
function.
Marco Corvi - 2003