Poster of Linux kernelThe best gift for a Linux geek
 Linux kernel map 
⇦ prev ⇱ home next ⇨

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;
}

    ⇦ prev ⇱ home next ⇨
    Poster of Linux kernelThe best gift for a Linux geek