18.3. TTY Line Settings
When a user wants to change
the line settings of a tty device or retrieve the current line
settings, he makes one of the many different termios user-space
library function calls or directly makes an
ioctl call on the tty device node. The tty core
converts both of these interfaces into a number of different tty
driver function callbacks and ioctl calls.
18.3.1. set_termios
The majority of the
termios
user-space functions are translated by the library into an
ioctl call to the driver node. A large number of
the different tty ioctl calls are then
translated by the tty core into a single
set_termios function call to the tty driver. The
set_termios callback needs to determine which
line settings it is being asked to change, and then make those
changes in the tty device. The tty driver must be able to decode all
of the different settings in the termios structure and react to any
needed changes. This is a complicated task, as all of the line
settings are packed into the termios structure in a wide variety of
ways.
The first thing that a set_termios function
should do is determine whether anything actually has to be changed.
This can be done with the following code:
unsigned int cflag;
cflag = tty->termios->c_cflag;
/* check that they really want us to change something */
if (old_termios) {
if ((cflag = = old_termios->c_cflag) &&
(RELEVANT_IFLAG(tty->termios->c_iflag) = =
RELEVANT_IFLAG(old_termios->c_iflag))) {
printk(KERN_DEBUG " - nothing to change...\n");
return;
}
}
The
RELEVANT_IFLAG
macro is defined as:
#define RELEVANT_IFLAG(iflag) ((iflag) & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))
and is used to mask off the important bits of the
cflags variable. This is then compared to the old
value, and see if they differ. If not, nothing needs to be changed,
so we return. Note that the old_termios variable
is first checked to see if it points to a valid structure first,
before it is accessed. This is required, as sometimes this variable
is set to NULL. trying to access a field off of a
NULL pointer causes the kernel to panic.
To look at the requested byte size, the
CSIZE
bitmask can be used to separate out
the proper bits from the cflag variable. If the
size can not be determined, it is customary to default to eight data
bits. This can be implemented as follows:
/* get the byte size */
switch (cflag & CSIZE) {
case CS5:
printk(KERN_DEBUG " - data bits = 5\n");
break;
case CS6:
printk(KERN_DEBUG " - data bits = 6\n");
break;
case CS7:
printk(KERN_DEBUG " - data bits = 7\n");
break;
default:
case CS8:
printk(KERN_DEBUG " - data bits = 8\n");
break;
}
To determine the requested parity value, the
PARENB bitmask can be checked against the
cflag variable to tell if any parity is to be set
at all. If so, the
PARODD bitmask can be used to determine if the
parity should be odd or even. An implementation of this is:
/* determine the parity */
if (cflag & PARENB)
if (cflag & PARODD)
printk(KERN_DEBUG " - parity = odd\n");
else
printk(KERN_DEBUG " - parity = even\n");
else
printk(KERN_DEBUG " - parity = none\n");
The stop bits that are requested can also be determined from the
cflag variable using the
CSTOPB bitmask. An implemention of this is:
/* figure out the stop bits requested */
if (cflag & CSTOPB)
printk(KERN_DEBUG " - stop bits = 2\n");
else
printk(KERN_DEBUG " - stop bits = 1\n");
There are a two basic types of flow control: hardware and software.
To determine if the user is asking for hardware flow control, the
CRTSCTS bitmask can be checked against the
cflag variable. An exmple of this is:
/* figure out the hardware flow control settings */
if (cflag & CRTSCTS)
printk(KERN_DEBUG " - RTS/CTS is enabled\n");
else
printk(KERN_DEBUG " - RTS/CTS is disabled\n");
Determining the different modes of software flow control and the
different stop and start characters is a bit more involved:
/* determine software flow control */
/* if we are implementing XON/XOFF, set the start and
* stop character in the device */
if (I_IXOFF(tty) || I_IXON(tty)) {
unsigned char stop_char = STOP_CHAR(tty);
unsigned char start_char = START_CHAR(tty);
/* if we are implementing INBOUND XON/XOFF */
if (I_IXOFF(tty))
printk(KERN_DEBUG " - INBOUND XON/XOFF is enabled, "
"XON = %2x, XOFF = %2x", start_char, stop_char);
else
printk(KERN_DEBUG" - INBOUND XON/XOFF is disabled");
/* if we are implementing OUTBOUND XON/XOFF */
if (I_IXON(tty))
printk(KERN_DEBUG" - OUTBOUND XON/XOFF is enabled, "
"XON = %2x, XOFF = %2x", start_char, stop_char);
else
printk(KERN_DEBUG" - OUTBOUND XON/XOFF is disabled");
}
Finally, the baud rate needs to be determined. The tty core provides
a function,
tty_get_baud_rate
,
to help do this. The function returns an integer indicating the
requested baud rate for the specific tty device:
/* get the baud rate wanted */
printk(KERN_DEBUG " - baud rate = %d", tty_get_baud_rate(tty));
Now that the tty driver has determined all of the different line
settings, it can set the hardware up properly based on these values.
18.3.2. tiocmget and tiocmset
In the 2.4 and older kernels, there used to be a number of tty
ioctl calls to get and set the different control
line settings. These were denoted by the constants
TIOCMGET, TIOCMBIS,
TIOCMBIC, and TIOCMSET.
TIOCMGET was used to get the line setting values
of the kernel, and as of the 2.6 kernel, this
ioctl call has been turned into a tty driver
callback function called tiocmget. The other
three ioctls have been simplified and are now
represented with a single tty driver callback function called
tiocmset
.
The tiocmget
function in the tty driver is called by the tty core when the core
wants to know the current physical values of the control lines of a
specific tty device. This is usually done to retrieve the values of
the DTR and RTS lines of a serial port. If the tty driver cannot
directly read the MSR or MCR registers of the serial port, because
the hardware does not allow this, a copy of them should be kept
locally. A number of the USB-to-serial drivers must implement this
kind of "shadow" variable. Here is
how this function could be implemented if a local copy of these
values are kept:
static int tiny_tiocmget(struct tty_struct *tty, struct file *file)
{
struct tiny_serial *tiny = tty->driver_data;
unsigned int result = 0;
unsigned int msr = tiny->msr;
unsigned int mcr = tiny->mcr;
result = ((mcr & MCR_DTR) ? TIOCM_DTR : 0) | /* DTR is set */
((mcr & MCR_RTS) ? TIOCM_RTS : 0) | /* RTS is set */
((mcr & MCR_LOOP) ? TIOCM_LOOP : 0) | /* LOOP is set */
((msr & MSR_CTS) ? TIOCM_CTS : 0) | /* CTS is set */
((msr & MSR_CD) ? TIOCM_CAR : 0) | /* Carrier detect is set*/
((msr & MSR_RI) ? TIOCM_RI : 0) | /* Ring Indicator is set */
((msr & MSR_DSR) ? TIOCM_DSR : 0); /* DSR is set */
return result;
}
The tiocmset function in the tty driver is
called by the tty core when the core wants to set the values of the
control lines of a specific tty device. The tty core tells the tty
driver what values to set and what to clear, by passing them in two
variables: set and clear. These
variables contain a bitmask of the lines settings that should be
changed. An ioctl call never asks the driver to
both set and clear a particular bit at the same time, so it does not
matter which operation occurs first. Here is an example of how this
function could be implemented by a tty driver:
static int tiny_tiocmset(struct tty_struct *tty, struct file *file,
unsigned int set, unsigned int clear)
{
struct tiny_serial *tiny = tty->driver_data;
unsigned int mcr = tiny->mcr;
if (set & TIOCM_RTS)
mcr |= MCR_RTS;
if (set & TIOCM_DTR)
mcr |= MCR_RTS;
if (clear & TIOCM_RTS)
mcr &= ~MCR_RTS;
if (clear & TIOCM_DTR)
mcr &= ~MCR_RTS;
/* set the new MCR value in the device */
tiny->mcr = mcr;
return 0;
}
|