重温旧的 API
tty 层一直是周围最稳定的内核 API 之一,而且表现良好。缺乏适当的引用计数、在需要的地方强加锁定以及 tty 设备分配的奇怪方式都归因于它的年代久远。值得庆幸的是,Al Viro 最近在 2.5 内核系列中清理了 tty 层中的许多旧的无用代码。因此,对于任何想要编写新的 tty 驱动程序的人来说,许多事情都发生了变化。
在 2002 年 8 月和 10 月的 LJ 期刊 [可在 /article/5896 和 /article/6226 获取] 中,我们讨论了 tty 层以及如何使用所有必要的功能回调来填充 struct tty_driver 结构。从那时起,创建了一个新的结构 struct tty_operations,用于保存所有功能回调。struct tty_driver 仍然包含较旧的函数指针,因此创建了一个新函数 tty_set_operations 来复制这些指针。希望这种重复很快就会消除。
许多变量已从 struct tty_driver 中移除。table、termios、termios_locked 和 refcount 字段已消失。tty 层现在处理所有适当的锁定和引用计数,而无需强制各个 tty 驱动程序为这些锁分配空间。
magic 和 num 变量不再需要由 tty 驱动程序显式设置。这些变量现在在一个新的函数 alloc_tty_driver 中设置,所有 tty 驱动程序必须调用该函数来为 tty 驱动程序分配空间。此驱动程序将要支持的不同 tty 设备的数量作为参数传递给该函数。例如,原始 tty 文章中介绍的 tiny tty 驱动程序将创建 struct tty_driver 为
/* allocate the tty driver */ tiny_tty_driver = alloc_tty_driver(TINY_TTY_MINORS);
过去,为 tty 驱动程序选择合适的名称很困难,因为 devfs 内核选项重载了 name 字段的用法。Christoph Hellwig 最终通过在 struct tty_driver 结构中引入一个名为 devfs_name 的新变量来解决了这个混乱。现在,name 字段应设置为一个简单的、小的名称,该名称显示在 tty proc 文件中。devfs_name 应设置为 devfs 用于为此驱动程序生成设备节点的名称。
在 2.5 内核系列中,MOD_INC_USE_COUNT 和 MOD_DEC_USE_COUNT 宏被声明为过于竞争,并且内核中几乎所有对它们的使用都被删除。为了做到这一点,模块引用计数被推高了一层,高于原始调用。这允许内核在跳转到模块之前增加模块的引用计数。同样,当内核完成模块时,它知道自动递减计数。
此模块更改在 tty 层中完成,因此没有 tty 驱动程序应包含 MOD_* 宏。相反,在 struct tty_driver 中添加了一个 owner 变量,以显示哪个模块拥有 tty 驱动程序。以下行显示如何正确设置此变量
tiny_tty_driver->owner = THIS_MODULE;
这告诉 tty 核心哪个模块与此 tty 驱动程序相关。
对于这些 tty 更改,以下是如何正确初始化 tty 驱动程序并在内核中注册它
#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 呼叫设备现在也已完全从内核中删除。2.5 内核树中任何使用呼叫支持的 tty 驱动程序都已将其删除。
除了 tty 结构更改之外,还移除了一些 tty ioctl,特别是 TIOCMGET、TIOCMBIS、TIOCMBIC 和 TIOCMSET ioctl。它们已被添加到 struct tty_operations 结构中的两个新函数回调 tiocmget 和 tiocmset 替换。这些函数定义为
int (*tiocmget)(struct tty_struct *tty, struct file *file); int (*tiocmset)(struct tty_struct *tty, struct file *file, unsigned int set, unsigned int clear);
当 tty 核心或用户想要知道特定 tty 端口的当前线路设置是什么时,将调用 tiocmget 函数。这几乎与旧的 TIOCMGET ioctl 调用工作方式相同。线路状态定义为不同的 MSR_* 值,但它们只是从函数调用返回,而不是像旧的 ioctl 要求驱动程序那样复制回用户空间。这是一个 tiocmget 函数可能看起来如何的示例
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;
当 tty 核心或用户想要设置或清除任何不同的线路设置时,将调用 tiocmset 函数。这个单一函数取代了 TIOCMBIS、TIOCMBIC 和 TIOCMSET ioctl 调用。此函数中的 set 和 clear 变量用于告知要设置哪些线路设置以及要清除哪些线路设置。不能同时要求清除和设置相同的线路设置,因此变量的处理顺序无关紧要。这是一个 tiocmset 函数的示例
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; }
usbserial 核心也受到这些 tty 核心更改的一些影响。tiocmget 和 tiocmset 函数已添加到 struct usb_serial_device_type 结构中。如果 usbserial 驱动程序提供这些回调,则对这些函数的 tty 调用将传递到较低的 usbserial 驱动程序。
Greg Kroah-Hartman 目前是各种不同驱动程序子系统的 Linux 内核维护者。他在 IBM 工作,从事与 Linux 内核相关的工作,可以通过 greg@kroah.com 联系到他。