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

18.1. A Small TTY Driver

To explain how the tty core works, we create a small tty driver that can be loaded, written to and read from, and unloaded. The main data structure of any tty driver is the struct tty_driver. It it used to register and unregister a tty driver with the tty core and is described in the kernel header file <linux/tty_driver.h>.

To create a struct tty_driver, the function alloc_tty_driver must be called with the number of tty devices this driver supports as the paramater. This can be done with the following brief code:

/* allocate the tty driver */
tiny_tty_driver = alloc_tty_driver(TINY_TTY_MINORS);
if (!tiny_tty_driver)
    return -ENOMEM;

After the alloc_tty_driver function is successfully called, the struct tty_driver should be initialized with the proper information based on the needs of the tty driver. This structure contains a lot of different fields, but not all of them have to be initialized in order to have a working tty driver. Here is an example that shows how to initialize the structure and sets up enough of the fields to create a working tty driver. It uses the tty_set_operations function to help copy over the set of function operations that is defined in the driver:

static struct tty_operations serial_ops = {
    .open = tiny_open,
    .close = tiny_close,
    .write = tiny_write,
    .write_room = tiny_write_room,
    .set_termios = tiny_set_termios,
};

...

    /* initialize the tty driver */
    tiny_tty_driver->owner = THIS_MODULE;
    tiny_tty_driver->driver_name = "tiny_tty";
    tiny_tty_driver->name = "ttty";
    tiny_tty_driver->devfs_name = "tts/ttty%d";
    tiny_tty_driver->major = TINY_TTY_MAJOR,
    tiny_tty_driver->type = TTY_DRIVER_TYPE_SERIAL,
    tiny_tty_driver->subtype = SERIAL_TYPE_NORMAL,
    tiny_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS,
    tiny_tty_driver->init_termios = tty_std_termios;
    tiny_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
    tty_set_operations(tiny_tty_driver, &serial_ops);

The variables and functions listed above, and how this structure is used, are explained in the rest of the chapter.

To register this driver with the tty core, the struct tty_driver must be passed to the tty_register_driver function:

/* register the tty driver */
retval = tty_register_driver(tiny_tty_driver);
if (retval) {
    printk(KERN_ERR "failed to register tiny tty driver");
    put_tty_driver(tiny_tty_driver);
    return retval;
}

When tty_register_driver is called, the kernel creates all of the different sysfs tty files for the whole range of minor devices that this tty driver can have. If you use devfs (not covered in this book) and unless the TTY_DRIVER_NO_DEVFS flag is specified, devfs files are created, too. The flag may be specified if you want to call tty_register_device only for the devices that actually exist on the system, so the user always has an up-to-date view of the devices present in the kernel, which is what devfs users expect.

After registering itself, the driver registers the devices it controls through the tty_register_device function. This function has three arguments:

  • A pointer to the struct tty_driver that the device belongs to.

  • The minor number of the device.

  • A pointer to the struct device that this tty device is bound to. If the tty device is not bound to any struct device, this argument can be set to NULL.

Our driver registers all of the tty devices at once, as they are virtual and not bound to any physical devices:

for (i = 0; i < TINY_TTY_MINORS; ++i)
    tty_register_device(tiny_tty_driver, i, NULL);

To unregister the driver with the tty core, all tty devices that were registered by calling tty_register_device need to be cleaned up with a call to tty_unregister_device. Then the struct tty_driver must be unregistered with a call to tty_unregister_driver:

for (i = 0; i < TINY_TTY_MINORS; ++i)
    tty_unregister_device(tiny_tty_driver, i);
tty_unregister_driver(tiny_tty_driver);

18.1.1. struct termios

The init_termios variable in the struct tty_driver is a struct termios. This variable is used to provide a sane set of line settings if the port is used before it is initialized by a user. The driver initializes the variable with a standard set of values, which is copied from the tty_std_termios variable. tty_std_termios is defined in the tty core as:

struct termios tty_std_termios = {
    .c_iflag = ICRNL | IXON,
    .c_oflag = OPOST | ONLCR,
    .c_cflag = B38400 | CS8 | CREAD | HUPCL,
    .c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK |
               ECHOCTL | ECHOKE | IEXTEN,
    .c_cc = INIT_C_CC
};

The struct termios structure is used to hold all of the current line settings for a specific port on the tty device. These line settings control the current baud rate, data size, data flow settings, and many other values. The different fields of this structure are:

tcflag_t c_iflag;

The input mode flags

tcflag_t c_oflag;

The output mode flags

tcflag_t c_cflag;

The control mode flags

tcflag_t c_lflag;

The local mode flags

cc_t c_line;

The line discipline type

cc_t c_cc[NCCS];

An array of control characters

All of the mode flags are defined as a large bitfield. The different values of the modes, and what they are used for, can be seen in the termios manpages available in any Linux distribution. The kernel provides a set of useful macros to get at the different bits. These macros are defined in the header file include/linux/tty.h.

All the fields that were defined in the tiny_tty_driver variable are necessary to have a working tty driver. The owner field is necessary in order to prevent the tty driver from being unloaded while the tty port is open. In previous kernel versions, it was up to the tty driver itself to handle the module reference counting logic. But kernel programmers determined that it would to be difficult to solve all of the different possible race conditions, and so the tty core now handles all of this control for the tty drivers.

The driver_name and name fields look very similar, yet are used for different purposes. The driver_name variable should be set to something short, descriptive, and unique among all tty drivers in the kernel. This is because it shows up in the /proc/tty/drivers file to describe the driver to the user and in the sysfs tty class directory of tty drivers currently loaded. The name field is used to define a name for the individual tty nodes assigned to this tty driver in the /dev tree. This string is used to create a tty device by appending the number of the tty device being used at the end of the string. It is also used to create the device name in the sysfs /sys/class/tty/ directory. If devfs is enabled in the kernel, this name should include any subdirectory that the tty driver wants to be placed into. As an example, the serial driver in the kernel sets the name field to tts/ if devfs is enabled and ttyS if it is not. This string is also displayed in the /proc/tty/drivers file.

As we mentioned, the /proc/tty/drivers file shows all of the currently registered tty drivers. With the tiny_tty driver registered in the kernel and no devfs, this file looks something like the following:

$ cat /proc/tty/drivers
tiny_tty             /dev/ttty     240     0-3 serial
usbserial            /dev/ttyUSB   188   0-254 serial
serial               /dev/ttyS       4  64-107 serial
pty_slave            /dev/pts      136   0-255 pty:slave
pty_master           /dev/ptm      128   0-255 pty:master
pty_slave            /dev/ttyp       3   0-255 pty:slave
pty_master           /dev/pty        2   0-255 pty:master
unknown              /dev/vc/        4    1-63 console
/dev/vc/0            /dev/vc/0       4       0 system:vtmaster
/dev/ptmx            /dev/ptmx       5       2 system
/dev/console         /dev/console    5       1 system:console
/dev/tty             /dev/tty        5       0 system:/dev/tty

Also, the sysfs directory /sys/class/tty looks something like the following when the tiny_tty driver is registered with the tty core:

$ tree /sys/class/tty/ttty*
/sys/class/tty/ttty0
`-- dev
/sys/class/tty/ttty1
`-- dev
/sys/class/tty/ttty2
`-- dev
/sys/class/tty/ttty3
`-- dev

$ cat /sys/class/tty/ttty0/dev 
240:0

The major variable describes what the major number for this driver is. The type and subtype variables declare what type of tty driver this driver is. For our example, we are a serial driver of a "normal" type. The only other subtype for a tty driver would be a "callout" type. Callout devices were traditionally used to control the line settings of a device. The data would be sent and received through one device node, and any line setting changes would be sent to a different device node, which was the callout device. This required the use of two minor numbers for every single tty device. Thankfully, almost all drivers handle both the data and line settings on the same device node, and the callout type is rarely used for new drivers.

The flags variable is used by both the tty driver and the tty core to indicate the current state of the driver and what kind of tty driver it is. Several bitmask macros are defined that you must use when testing or manipulating the flags. Three bits in the flags variable can be set by the driver:

TTY_DRIVER_RESET_TERMIOS

This flag states that the tty core resets the termios setting whenever the last process has closed the device. This is useful for the console and pty drivers. For instance, suppose the user leaves a terminal in a weird state. With this flag set, the terminal is reset to a normal value when the user logs out or the process that controlled the session is "killed."

TTY_DRIVER_REAL_RAW

This flag states that the tty driver guarantees to send notifications of parity or break characters up-to-the-line discipline. This allows the line discipline to process received characters in a much quicker manner, as it does not have to inspect every character received from the tty driver. Because of the speed benefits, this value is usually set for all tty drivers.

TTY_DRIVER_NO_DEVFS

This flag states that when the call to tty_register_driver is made, the tty core does not create any devfs entries for the tty devices. This is useful for any driver that dynamically creates and destroys the minor devices. Examples of drivers that set this are the USB-to-serial drivers, the USB modem driver, the USB Bluetooth tty driver, and a number of the standard serial port drivers.

When the tty driver later wants to register a specific tty device with the tty core, it must call tty_register_device, with a pointer to the tty driver, and the minor number of the device that has been created. If this is not done, the tty core still passes all calls to the tty driver, but some of the internal tty-related functionality might not be present. This includes /sbin/hotplug notification of new tty devices and sysfs representation of the tty device. When the registered tty device is removed from the machine, the tty driver must call tty_unregister_device.

The one remaining bit in this variable is controlled by the tty core and is called TTY_DRIVER_INSTALLED. This flag is set by the tty core after the driver has been registered and should never be set by a tty driver.

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