Revisiting Old APIs
It has been over a year since this column started, and due to the rapid rate of Linux kernel development, much of what was written back then is now wrong. This month, we cover the changes in the different kernel APIs that have been discussed previously.
The tty layer had been one of the most stable kernel APIs around, and it showed. The lack of proper reference counting, the locking shoved in where it looked needed and the strange way tty devices were allocated are all due to its age. Thankfully, Al Viro recently cleaned up a lot of the old cruft in the tty layer in the 2.5 kernel series. Because of this, a number of things have changed for anyone wanting to write a new tty driver.
In the August and October 2002 issues of LJ [available at /article/5896 and /article/6226], we discussed the tty layer and how to fill up the struct tty_driver structure with all the necessary function callbacks. Since then, a new structure, struct tty_operations, has been created to hold all of the function callbacks. The struct tty_driver still contains the older function pointers, so a new function has been created to copy these pointers over, tty_set_operations. Hopefully, this duplication will be eliminated soon.
A number of variables have been removed from the struct tty_driver. The table, termios, termios_locked and refcount fields are gone. The tty layer now handles all of the proper locking and reference counting, without forcing the individual tty drivers to allocate the space for these locks.
The magic and num variables no longer need to be set explicitly by the tty driver. These variables now are set in a new function, alloc_tty_driver, that must be called by all tty drivers to allocate the space for the tty driver. The number of different tty devices this driver is going to support is passed as a parameter to the function. For example, the tiny tty driver introduced in the original tty articles would create the struct tty_driver as:
/* allocate the tty driver */ tiny_tty_driver = alloc_tty_driver(TINY_TTY_MINORS);
In the past, picking the proper name for the tty driver was difficult, as the devfs kernel option overloaded the use of the name field. Christoph Hellwig finally fixed this mess by introducing a new variable in the struct tty_driver structure called devfs_name. Now, the name field should be set to a simple, small name that shows up in the tty proc files. The devfs_name should be set to the name that devfs uses to generate the device node for this driver.
In the 2.5 kernel series, the MOD_INC_USE_COUNT and MOD_DEC_USE_COUNT macros were declared too racy, and almost all usage of them in the kernel was removed. In order to do this, module reference counting was pushed a layer higher than the original calls. This allows the kernel to increment a module's reference count before the kernel jumps into the module. Likewise, when the kernel is finished with the module, it knows to decrement the count automatically.
This module change was done in the tty layer, so no tty driver should contain the MOD_* macros. Instead, an owner variable was added to the struct tty_driver to show what module owns the tty driver. The following line shows how to set this variable properly:
tiny_tty_driver->owner = THIS_MODULE;
This tells the tty core what module is related to this tty driver.
As for these tty changes, here is how to initialize and register a tty driver with the kernel properly:
#define TINY_TTY_MAJOR240 /* experimental range */ #define TINY_TTY_MINORS 255 /* use the whole major up */ static struct tty_operations serial_ops = { .open = tiny_open, .close = tiny_close, .write = tiny_write, .write_room = tiny_write_room, }; static struct tty_driver *tiny_tty_driver; static int __init tiny_init(void) { /* allocate the tty driver */ tiny_tty_driver = alloc_tty_driver(TINY_TTY_MINORS); /* 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); if (tty_register_driver(tiny_tty_driver)) { printk(KERN_ERR "failed to register tiny tty driver"); return -1; } printk(KERN_INFO DRIVER_DESC " " DRIVER_VERSION); return 0; } static void __exit tiny_exit (void) { tty_unregister_driver(tiny_tty_driver); } module_init(tiny_init); module_exit(tiny_exit);
tty callout devices also are now removed entirely from the kernel. Any tty driver living in the 2.5 kernel tree that used callout support has had it removed.
Along with the tty structure changes, a few tty ioctls have been removed, specifically the TIOCMGET, TIOCMBIS, TIOCMBIC and TIOCMSET ioctls. They have been replaced with two new function callbacks, tiocmget and tiocmset, that have been added to the struct tty_operations structure. These functions are defined as:
int (*tiocmget)(struct tty_struct *tty, struct file *file); int (*tiocmset)(struct tty_struct *tty, struct file *file, unsigned int set, unsigned int clear);
The tiocmget function is called when the tty core or a user wants to know what the current line settings are for a specific tty port. This works almost exactly like the old TIOCMGET ioctl call did. The line status is defined as the different MSR_* values, but instead of being copied back into user space, as the old ioctl required the driver to do, they merely are returned from the function call. Here's an example of how a tiocmget function could look:
int tiny_tiocmget(struct tty_struct *tty, struct file *file) { struct tiny_private *tp = tty->private; unsigned int msr = tp->msr; unsigned int mcr = tp->mcr; unsigned int result = 0; result = ((mcr & MCR_DTR) ? TIOCM_DTR: 0) /* DTR is set */ | ((mcr & MCR_RTS) ? TIOCM_RTS: 0) /* RTS 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 is called when the tty core or a user wants to set or clear any of the different line settings. This single function replaces the TIOCMBIS, TIOCMBIC and TIOCMSET ioctl calls. The set and clear variables in this function are used to tell which line settings to set and which to clear. The same line setting cannot be asked to be cleared and set at the same time, so the order in which variables are processed does not matter. An example of the tiocmset function is:
int tiny_tiocmset(struct tty_struct *tty, struct file *file, unsigned int set, unsigned int clear) { struct tiny_private *tp = tty->private; if (set & TIOCM_RTS) mcr |= MCR_RTS; if (set & TIOCM_DTR) mcr |= MCR_RTS; if (set & TIOCM_LOOP) mcr |= MCR_LOOPBACK; if (clear & TIOCM_RTS) mcr &= ~MCR_RTS; if (clear & TIOCM_DTR) mcr &= ~MCR_RTS; if (clear & TIOCM_LOOP) mcr &= ~MCR_LOOPBACK; /* set the new MCR value in the device */ tp->mcr = mcr; return 0; }
The usbserial core also has been affected a bit by these tty core changes. The tiocmget and tiocmset functions have been added to the struct usb_serial_device_type structure. The tty calls to these functions are passed down to the lower usbserial drivers, if the usbserial driver provides those callbacks.
I would like to thank Al Viro, Christoph Hellwig and Russell King for finally starting to work on cleaning up the tty layer to raise it to the proper standards of the rest of the kernel. Their changes have been instrumental in simplifying the tty driver interface, allowing driver authors to focus on the specific hardware implementations and not worry about the kernel interactions as much as before.
Greg Kroah-Hartman currently is the Linux kernel maintainer for a variety of different driver subsystems. He works for IBM, doing Linux kernel-related things, and can be reached at greg@kroah.com.