The tty Layer, Part II

write() Rules

The write() callback in your tty_struct can be called from both interrupt context and user context. This is important to know, as you should not call any function that might sleep when you are in interrupt context. This includes any function that possibly might call schedule(), such as the common functions like copy_from_user(), kmalloc() and printk(). If you really want to sleep, then please check your status by calling in_interrupt().

The write() callback can be called when the tty subsystem itself needs to send some data through the tty device. This can happen if you do not implement the put_char() function in the tty_struct. (Remember, if there is no put_char() function, the tty layer will use the write() function.) This commonly happens when the tty layer wants to convert a newline character to a linefeed plus a newline character. The main point to remember here is your write() function must not return 0 for this kind of call. This means that you must write that byte of data to the device, as the caller (the tty layer) will NOT buffer the data and try again at a later time. As the write() call cannot determine if it is being called in place of put_char()--even if only one byte of data is being sent—please try to implement your write() call always to be able to accept at least one byte of data. A number of the current USB-to-serial tty drivers do not follow this rule, and as a result some terminal types do not work properly when connected to them.

set_termios() Implementation

In order to properly implement the set_termios() callback, your driver must be able to decode all of the different settings in the termios structure. This is a complicated task, because all of the line settings are packed into the termios structure in a wide variety of ways.

Listing 1 shows a simple implementation of the set_termios() call that will print, to the kernel debug log, all of the different line settings that have been requested by the user.

Listing 1. A Simple Implementation of the set_termios Call

First off, save a copy of the cflags variable from the tty structure, as we are going to be accessing it a lot:

        unsigned int cflag;
        cflag = tty->termios->c_cflag;

Next, test to see if there is anything we need to do. For example, see if the user is trying to use the same settings we currently have; we don't want to do any extra work if it's not necessary.

/* 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. Compare this value to the old value, and see if they differ. If they don't, nothing needs to be changed, so we return. Note that we first check that the old_termios variable actually points to something, before we try to access a field off of it. This check is required, because sometimes this variable will be NULL. Trying to access a field off of a NULL pointer will cause a nasty oops in the kernel.

Now that we know we need to change the terminal settings, let's look at the requested byte size:

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

We mask the cflag with the CSIZE bit field and test the result. If we can't figure out what bits were set, we can use the default of 8 data bits. Then we check for the requested parity value:

/* 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");
We first test to see if the user actually wants a type of parity set in the first place. If so, then we need to test which kind of parity (odd or even) they want.

The stop bits requested are also simple to test for:

/* figure out the stop bits requested */
if (cflag & CSTOPB)
    printk (KERN_DEBUG " - stop bits = 2\n");
else
    printk (KERN_DEBUG " - stop bits = 1\n");

Now, we're on to determining the proper flow control settings. It's a simple process to determine if we should use RTS/CTS:

/* figure out the 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, however, is a bit more difficult:
/* 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 we want to determine the baud rate. Luckily, the tty_get_baud_rate() function can extract the specific baud rate out of the termios settings and return it as an integer:
/* get the baud rate wanted */
printk (KERN_DEBUG " - baud rate = %d",
        tty_get_baud_rate(tty));
Now that all of the different line settings have been determined, it is up to you to use the information to set up the device properly.

______________________

Webinar
One Click, Universal Protection: Implementing Centralized Security Policies on Linux Systems

As Linux continues to play an ever increasing role in corporate data centers and institutions, ensuring the integrity and protection of these systems must be a priority. With 60% of the world's websites and an increasing share of organization's mission-critical workloads running on Linux, failing to stop malware and other advanced threats on Linux can increasingly impact an organization's reputation and bottom line.

Learn More

Sponsored by Bit9

Webinar
Linux Backup and Recovery Webinar

Most companies incorporate backup procedures for critical data, which can be restored quickly if a loss occurs. However, fewer companies are prepared for catastrophic system failures, in which they lose all data, the entire operating system, applications, settings, patches and more, reducing their system(s) to “bare metal.” After all, before data can be restored to a system, there must be a system to restore it to.

In this one hour webinar, learn how to enhance your existing backup strategies for better disaster recovery preparedness using Storix System Backup Administrator (SBAdmin), a highly flexible bare-metal recovery solution for UNIX and Linux systems.

Learn More

Sponsored by Storix