Go to main content

STREAMS Programming Guide

Exit Print View

Updated: November 2020
 
 

STREAMS-Based Terminal Subsystem

This chapter describes how a terminal subsystem is set up and how interrupts are handled. Different protocols are addressed, as well as canonical processing and line discipline substitution.

Overview of Terminal Subsystem

STREAMS provides a uniform interface for implementing character I/O devices and networking protocols in the kernel. The Oracle Solaris 11.4 software implements the terminal subsystem in STREAMS. STREAMS-Based Terminal Subsystem shows the STREAMS-based terminal subsystem. It provides the following benefits:

  • Reusable line discipline modules. The same module can be used in many streams where the configuration of these streams may be different.

  • Line-discipline substitution. Although the Oracle Solaris operating environment provides a standard terminal line-discipline module, another one conforming to the interface can be substituted. For example, a remote login feature may use the terminal subsystem line discipline module to provide a terminal interface to the user.

  • Internationalization. The modularity and flexibility of the STREAMS-based terminal subsystem enables an easy implementation of a system that supports multiple-byte characters for internationalization. This modularity also allows easy addition of new features to the terminal subsystem.

  • Easy customizing. Users may customize their terminal subsystem environment by adding and removing modules of their choice.

  • The pseudo-terminal subsystem. The pseudo-terminal subsystem can be easily supported. For more information, see STREAMS-Based Pseudo-Terminal Subsystem.

  • Merge with networking. By pushing a line discipline module on a network line, you can make the network look like a terminal line.

Figure 38  STREAMS-Based Terminal Subsystem

image:Diagram shows the stream components of a STREAMS-based terminal             subsystem.

The initial setup of the STREAMS-based terminal subsystem is handled with the ttymon command command using the autopush feature. For more information, see STREAMS Administrative Driver and the ttymon(8) man page.

The STREAMS-based terminal subsystem supports termio, the termios specification of the POSIX standard, multiple-byte characters for internationalization, the interface to asynchronous hardware flow control and peripheral controllers for asynchronous terminals. XENIX and BSD compatibility are also provided by the ttcompat module. For more information, see the termio(4I), termios(3C), and ttcompat(4M) man pages.

Master Driver and Slave Driver Characteristics

The master driver and slave driver have the following characteristics:

  • Each master driver has one-to-one relationship with a slave device based on major/minor device numbers.

  • Only one open is allowed on a master device. Multiple opens are allowed on the slave device according to standard file mode and ownership permissions.

  • Each slave driver minor device has a node in the file system.

  • An open on a master device automatically locks out an open on the corresponding slave driver.

  • A slave cannot be opened unless the corresponding master is open and has unlocked the slave.

  • Starting in Oracle Solaris 11.4, to provide a TTY interface to the user, the ldterm, ptem, and ttcompat modules are automatically pushed on the slave side.

  • A close on the master sends a hang up to the slave and renders both streams unusable, after all data have been consumed by the process on the slave side.

  • The last close on the slave side sends a zero-length message to the master but does not sever the connection between the master and slave drivers.

Line-Discipline Module

A STREAMS line-discipline module called ldterm is a key part of the STREAMS-based terminal subsystem. Throughout this chapter, the terms line discipline and ldterm are used interchangeably and refer to the STREAMS version of the standard line discipline and not the traditional character version. ldterm performs the standard terminal I/O processing traditionally done through the linesw mechanism.

The termio and termios specifications describe four flags that are used to control the terminal:

  • c_iflag defines input modes

  • c_oflag defines output modes

  • c_cflag defines hardware control modes

  • c_lflag defines terminal functions used by ldterm

To process these flags elsewhere (for example, in the firmware or in another process), a mechanism is in place to turn the processing of these flags on and off. When ldterm is pushed, it sends an M_CTL message downstream that asks the driver which flags the driver will process. The driver sends back that message in response if it needs to change the ldterm default processing. By default, ldterm assumes that it must process all flags except c_cflag unless it receives a message indicating otherwise. For more information, see the ldterm(4M) man page.

Default Settings

When ldterm is pushed on the stream, the open routine initializes the settings of the termio flags. The default settings are:

c_iflag = BRKINT|ICRNL|IXON|IMAXBEL
c_oflag = OPOST|ONLCR|TAB3
c_cflag = CREAD|CS8|B9600
c_lflag = ISIG|ICANON|ECHO|ECHOK|IEXTEN|ECHOE|ECHOKE | ECHOCTL

In canonical mode where ICANON flag in c_lflag is turned on, read from the terminal file descriptor is in message non-discard (RMSGN) mode. For more information, see streamio(4I). This implies that in canonical mode, read on the terminal file descriptor always returns at most one line, regardless of how many characters have been requested. In non-canonical mode, read is in byte-stream (RNORM) mode. The flag ECHOCTL has been added for SunOS 4.1 compatibility.

For information about user-configurable settings, see termio(4I).

Module open and close Routines

The open routine of the ldterm module allocates space for holding the TTY structure by allocating a buffer from the STREAMS buffer pool. For more information, see ldtermstd_state_t in ldterm.h. The number of modules that can be pushed on one stream, as well as the number of TTYs in use, is limited. The number of instances of ldterm that have been pushed is limited only by available memory. The open also sends an M_SETOPTS message upstream to set the stream head high-water and low-water marks to 1024 and 200, respectively. These are the current values. For more information, see the ldterm(4M) man page.

The ldterm module identifies itself as a TTY to the stream head by sending an M_SETOPTS message upstream with the SO_ISTTY bit of so_flags set. The stream head allocates the controlling TTY on the open, if one is not already allocated.

To maintain compatibility with existing applications that use the O_NDELAY flag, the open routine sets the SO_NDELON flag on in the so_flags field of the stroptions(9S) structure in the M_SETOPTS message.

The open routine fails if there are no buffers available (so it cannot allocate the internal state structure) or when an interrupt occurs while waiting for a buffer to become available.

The close routine frees all the outstanding buffers allocated by this stream. It also sends an M_SETOPTS message to the stream head to undo the changes made by the open routine. The ldterm module also sends M_START messages downstream to undo the effect of any previous M_STOP messages.

Read-Side Processing

The ldterm module's read-side processing has put and service procedures. High-water and low-water marks for the read queue are 1024 and 200, respectively. These are the current values.

ldterm can send the following messages upstream:

M_DATA, M_BREAK, M_PCSIG, M_SIG, M_FLUSH, M_ERROR, M_IOCACK, M_IOCNAK, M_HANGUP, M_CTL, M_SETOPTS, M_COPYOUT, and M_COPYIN.

The ldterm module's read side processes M_BREAK, M_DATA, M_CTL, M_FLUSH, M_HANGUP, M_IOCACK and M_IOCNAK messages. All other messages are sent upstream unchanged.

The put procedure scans the message for flow-control characters (IXON), signal-generating characters, and, after (possible) transformation of the message, queues the message for the service procedure. Echoing is handled completely by the service procedure.

If the ICANON flag is on in c_lflag, canonical processing is performed. If the ICANON flag is off, non-canonical processing is performed. Handling of VMIN/VTIME in the STREAMS environment is somewhat complicated, because read needs to activate a timer in the ldterm module in some cases; hence, read notification becomes necessary. When a user issues an ioctl to put ldterm in non-canonical mode, the module sends an M_SETOPTS message to the stream head to register read notification. Further reads on the terminal file descriptor will cause the stream head to issue an M_READ message downstream and data will be sent upstream in response to the M_READ message. With read notification, buffering of raw data is performed by ldterm. Canonizing the raw data when the user has switched from raw to canonical mode is possible. However, the reverse is not possible.

To summarize, in non-canonical mode, the ldterm module buffers all data until VMIN or VTIME criteria are met. For example, if VMIN=3 and VTIME=0, and three bytes have been buffered, these characters are sent to the stream head regardless of whether there is a pending M_READ, and no M_READ needs to be sent downstream. If an M_READ message is received, the number of bytes sent upstream is the argument of the M_READ message unless VTIME is satisfied before VMIN (for example. the timer has expired), in which case whatever characters are available will be sent upstream.

The service procedure of ldterm handles STREAMS-related flow control. Because the read side high-water and low-water marks are 1024 and 200 respectively, placing 1024 characters or more on the read queue causes the QFULL flag be turned on, indicating that the module below should not send more data upstream.

Input flow control is regulated by the line-discipline module which generates M_STARTI and M_STOPI high priority messages. When sent downstream, receiving drivers or modules take appropriate action to regulate the sending of data upstream. Output flow control is activated when ldterm receives flow control characters in its data stream. The module then sets an internal flag indicating that output processing is to be restarted/stopped and sends an M_START/M_STOP message downstream.

Write-Side Processing

Write-side processing of the ldterm module is performed by the write-side put and service procedures.

The ldterm module supports the following ioctls:

TCSETA, TCSETAW, TCSETAF, TCSETS, TCSETSW, TCSETSF, TCGETA, TCGETS, TCXONC, TCFLSH, and TCSBRK.

All ioctls not recognized by the ldterm module are passed downstream to the neighboring module or driver.

The following messages can be received on the write side:

M_DATA, M_DELAY, M_BREAK, M_FLUSH, M_STOP, M_START, M_STOP, M_START, M_READ, M_IOCDATA, M_CTL, and M_IOCTL.

On the write side, the ldterm module processes M_FLUSH, M_DATA, M_IOCTL, and M_READ messages, and all other messages are passed downstream unchanged.

An M_CTL message is generated by ldterm as a query to the driver for an intelligent peripheral and to decide on the functional split for termio processing. If all or part of termio processing is done by the intelligent peripheral, ldterm can turn off this processing to avoid computational overhead. This is done by sending an appropriate response to the M_CTL message, as follows:

  • If all of the termio processing is done by the peripheral hardware, the driver sends an M_CTL message back to ldterm with ioc_cmd of the structure iocblk set to MC_NO_CANON. If ldterm is to handle all termio processing, the driver sends an M_CTL message with ioc_cmd set to MC_DO_CANON. The default is MC_DO_CANON. For more information, see the termio(4I) and iocblk(9S) man pages.

  • If the peripheral hardware handles only part of the termio processing, it informs ldterm in the following way:

    The driver for the peripheral device allocates an M_DATA message large enough to hold a termios structure. The driver then turns on those c_iflag, c_oflag, and c_lflag fields of the termios structure that are processed on the peripheral device by executing an OR operation on the flag values. The M_DATA message is then attached to the b_cont field of the M_CTL message it received. The message is sent back to ldterm with ioc_cmd in the data buffer of the M_CTL message set to MC_PART_CANON. For more information, see the ldterm(4M) and termios(3C) man pages.

One difference between AT&T STREAMS and Oracle Solaris STREAMS is that AT&T's line discipline module does not check whether write-side flow control is in effect before forwarding data downstream. It expects the downstream module or driver to add the messages to its queue until flow control is lifted. This is not true in Oracle Solaris STREAMS.

EUC Handling in ldterm

Post-processing (the o_flags) should not be handled by the host processor unless the board software is prepared to deal with international (EUC) character sets properly because that post-processing must take the EUC information into account. ldterm allots the appropriate screen width of characters (that is, how many columns are taken by characters from each given code set on the current physical display) and it takes this width into account when calculating tab expansions. When using multi-byte characters or multi-column characters ldterm automatically handles tab expansion (when TAB3 is set) and does not leave this handling to a lower module or driver.

By default, multi-byte handling by ldterm is turned off. When ldterm receives an EUC_WSET ioctl, it turns multi-byte processing on if it is essential to properly handle the indicated code set. Thus, if you use single byte 8-bit codes and have no special multi-column requirements, the special multi-column processing is not used at all. This means that multi-byte processing does not reduce the processing speed or efficiency of ldterm unless it is actually used.

The following describes how the EUC handling in ldterm works:

First, the multi-byte and multi-column character handling is only enabled when the EUC_WSET ioctl indicates that one of the following conditions is met:

  • Code set consists of more than one byte (including the SS2 and/or SS3) of characters

  • Code set requires more than one column to display on the current device, as indicated in the EUC_WSET structure

Assuming that one or more of the previous conditions exists, EUC handling is enabled. At this point, a parallel array (see ldterm_mod structure) used for other information is allocated and a pointer to it is stored in t_eucp_mp. The parallel array that it holds is pointed to by t_eucp. The t_codeset field holds the flag that indicates which of the code sets is currently being processed on the read side. When a byte with the high bit arrives, ldterm checks to see if it is SS2 or SS3. If yes, it belongs to code set 2 or 3. Otherwise, it is a byte that comes from code set 1.

Once the extended code set flag has been set, the input processor retrieves the subsequent bytes, as they arrive, to build one multi-byte character. The counter field t_eucleft tells the input processor how many bytes remain to be read for the current character. The parallel array t_eucp holds its display width for each logical character in the canonical buffer. During erase processing, positions in the parallel array are consulted to determine how many backspaces need to be send to erase each logical character. (In canonical mode, one backspace of input erases one logical character, no matter how many bytes or columns that character consumes.) This greatly simplifies erase processing for EUC.

The t_maxeuc field holds the maximum length, in memory bytes, of the EUC character mapping currently in use. The eucwioc field is a substructure that holds information about each extended code set.

The t_eucign field aids in output post-processing. When characters are output, ldterm keeps a column to indicate what the current cursor column is supposed to be. When it sends the first byte of an extended character, it adds the number of columns required for that character to the output column. It then subtracts one from the total width in memory bytes of that character and stores the result in t_eucign. This field tells ldterm how many subsequent bytes to ignore for the purposes of column calculation. ldterm calculates the appropriate number of columns when it sees the first byte of the character.

The field t_eucwarn is a counter for occurrences of bad extended characters. It is mostly useful for debugging. After receiving a certain number of illegal EUC characters, a warning is given on the system console. This is because of a problem on the line or with the declared values.

There are two relevant files for handling multi-byte characters: euc.h and eucioctl.h. eucioctl.h contains the structure that is passed with EUC_WSET and EUC_WGET calls. The normal way to use this structure is to get CSWIDTH from the locale using a mechanism such as getwidth or setlocale, copy the values into the structure in eucioctl.h, and send the structure using an I_STR ioctl. The EUC_WSET call informs the ldterm module about the number of bytes in extended characters and how many columns the extended characters from each set consume on the screen. This enables ldterm to treat multi-byte characters as single units for the purpose of erase processing and to correctly calculate tab expansions for multi-byte characters. For more information, see the getwidth(3C) and setlocale(3C) man pages.


Note -  Use LC_CTYPE instead of CSWIDTH in Oracle Solaris systems.

The file euc.h has fields for EUC width, screen width, and wide-character width. The functions in Example 68, EUC Header File are used to set and get EUC widths. These functions assume the environment where the eucwidth_t structure is needed and available.

Example 68  EUC Header File
#include <eucioctl.h>			/* need others,like stropts.h*/

struct eucioc eucw;			/*for EUC_WSET/WGET to line disc*/
eucwidth_t width;				/* ret struct from _getwidth() */
/*
 * set_euc					Send EUC code widths to line discipline.
 */
set_euc(struct eucioc *e)
{
	struct strioctl sb;

	sb.ic_cmd = EUC_WSET;
	sb.ic_timout = 15;
	sb.ic_len = sizeof(struct eucioc);
	sb.ic_dp = (char *) e;

	if (ioctl(0, I_STR, &sb) < 0)
			fail();
}
/*
 * euclook.   Get current EUC code widths from line discipline.
 */
euclook(struct eucioc *e)
{
	struct strioctl sb;

	sb.ic_cmd = EUC_WGET;
	sb.ic_timout = 15;
	sb.ic_len = sizeof(struct eucioc);
	sb.ic_dp = (char *) e;

	if (ioctl(0, I_STR, &sb) < 0)
			fail();

	printf("CSWIDTH=%d:%d,%d:%d,%d:%d",
			e->eucw[1], e->scrw[1],
			e->eucw[2], e->scrw[2],
			e->eucw[3], e->scrw[3]);
}

Hardware Emulation Module

If a stream supports a terminal interface, a driver or module that understands all ioctls is needed to support terminal semantics (specified by termio(4I) and termiox(4I). If there is no hardware driver that understands all ioctl commands downstream from the ldterm module, a hardware emulation module must be placed downstream from the line-discipline module. The function of the hardware emulation module is to understand and acknowledge the ioctls that may be sent to the process at the stream head and to mediate the passage of control information downstream. Together, the line-discipline module and the hardware emulation module behave as if there was an actual terminal on that stream.

The hardware emulation module is necessary whenever there is no TTY driver at the end of the stream. For example, the module is necessary in a pseudo-TTY situation where there is process-to-process communication on one system, or in a network situation where a termio interface is expected, for example, remote login, but there is no TTY driver on the stream. For more information, see STREAMS-Based Pseudo-Terminal Subsystem.

Most of the actions taken by the hardware emulation module are the same regardless of the underlying architecture. However, there are some actions that are different, depending on whether the communication is local or remote and whether the underlying transport protocol is used to support the remote connection.

Each hardware emulation module has an open, close, read queue put procedure, and write queue put procedure.

The hardware emulation module does the following:

  • Processes, if appropriate, and acknowledges receipt of the following ioctls on its write queue by sending an M_IOCACK message back upstream: TCSETA, TCSETAW, TCSETAF, TCSETS, TCSETSW, TCSETSF, TCGETA, TCGETS, and TCSBRK.

  • Acknowledges the Extended UNIX Code (EUC) ioctl. For more information, see the ioctl(2) man page.

  • If the environment supports windowing, it acknowledges the windowing TIOCSWINSZ, TIOCGWINSZ, and JWINSIZE ioctl(2)s. If the environment does not support windowing, an M_IOCNAK message is sent upstream.

  • If another ioctl is received on its write queue, it sends an M_IOCNAK message upstream. It doesn't pass any unrecognized ioctls to the slave driver.

  • When the hardware emulation module receives an M_IOCTL message of type TCSBRK on its write queue, it sends an M_IOCACK message upstream and the appropriate message downstream. For example, an M_BREAK message could be sent downstream.

  • When the hardware emulation module receives an M_IOCTL message on its write queue to set the baud rate to 0 (TCSETAW with CBAUD set to B0), it sends an M_IOCACK message upstream and an appropriate message downstream; for networking situations this will probably be an M_PROTO message, which is a TPI T_DISCON_REQ message requesting the transport provider to disconnect.

  • All other messages (M_DATA, for instance) not mentioned here are passed to the next module or driver in the stream.

The hardware emulation module processes messages in a way consistent with the driver that exists.