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.
|