Crea sito

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

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 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
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
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
STOP stop output VSTOP
INTR interrupt signal (SIGINT) VINTR
QUIT quit signal (SIGQUIT) VQUT
SUSP suspend signal (SIGTSTP) VSUSP
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,
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=0TIME=0 read() returns immediately with whatever data are available (from 0 if none, to the maximum number requested in the call)
MIN=0TIME>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>0TIME=0 read() returns between 1 and the requested number when there are data available (may wait forever)
MIN>0TIME>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

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 structures
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

Device driver, tty_driver

Line discipline, tty_ldisc

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:
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,


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.

mem_tty.c, memeory-based tty driver;
mem_read.c, tty driver read test;
mem_write.c, tty driver write test;
Makefile, tty driver write test;

Marco Corvi - 2003