Linux 设备驱动 (第三版)第 18 章 TTY 驱动18.3. TTY 线路设置
上一篇:18.2. tty_drive... 下一篇:18.4. ioctls 函数

18.3. TTY 线路设置

18.3. TTY 线路设置

当一个用户要改变一个 tty 设备的线路设置或者获取当前线路设置, 他调用一个许多的不同 termios 用户空间库函数或者直接对这个 tty 设备的节点调用 ioctl. tty 核心转换这 2 种接口为许多不同的 tty 驱动函数回调和 ioctl 调用.

18.3.1. set_termios 函数

大部分 termios 用户空间函数被库转换为一个对驱动节点的 ioctl 调用. 大量的不同的 tty ioctl 调用接着被 tty 核心转换为一个对 tty 驱动的单个 set_termios 函数调用. set_termios 调用需要决定哪个线路设置它被请求来改变, 接着在 tty 设备中做这些改变. tty 驱动必须能够解码所有的在 termios 结构中的不同设置并且响应任何需要的改变. 这是一个复杂的任务, 因为所有的线路设置以很多的方式被包装进 termios 结构.

一个 set_termios 函数应当做的第一件事情是决定任何事情是否真的需要改变. 这可使用下面的代码完成:


unsigned int cflag;
cflag = tty->termios->c_cflag;
/* 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;
        }
}

RELEVANT_IFLAG 宏定义为:


#define RELEVANT_IFLAG(iflag) ((iflag) & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))

而且用在屏蔽掉 cflags 变量的重要位. 接着这个和原来的值比较, 并且看是否它们不同. 如果不, 什么不改变, 因此我们返回. 注意 old_termios 变量是第一个被检查来看是否它指向一个有效的结构, 在它被存取之前. 这是需要的, 因为有时这个变量被设为 NULL. 试图存取一个 NULL 指针成员会导致内核崩溃.

为查看需要的字节大小, CSIZE 位掩码可用来从 cflag 变量区分出正确的位. 如果这个大小无法知道, 习惯上确实是 8 个数据位. 这个可如下实现:


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

为决定需要的奇偶值, PARENB 位掩码可对 cflag 变量检查来告知是否任何奇偶要被设置. 如果这样, PARODD 位掩码可用来决定是否奇偶应当是奇或者偶. 这个的一个实现是:


/* 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");

请求的停止位也可使用 CSTOPB 位掩码从 cflag 变量中来知道. 一个实现是:


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

有 2 个基本的流控类型: 硬件和软件. 为确定是否用户要求硬件流控, CRTSCTS 位掩码用来对 cflag 变量检查. 它的一个例子是:


/* figure out the hardware flow control settings */
if (cflag & CRTSCTS)
        printk(KERN_DEBUG " - RTS/CTS is enabled\n");
else
        printk(KERN_DEBUG " - RTS/CTS is disabled\n");

确定软件流控的不同模式和不同的起停字符是有些复杂:


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

最后, 波特率需要确定. tty 核心提供了一个函数, tty_get_baud_rate, 来帮助做这个. 这个函数返回一个整型数指示请求的波特率给特定的 tty 设备.


/* get the baud rate wanted */
printk(KERN_DEBUG " - baud rate = %d", tty_get_baud_rate(tty));

现在 tty 驱动已经确定了所有的不同的线路设置, 它可以基于这些值正确设置硬件.

18.3.2. tiocmget 和 tiocmset

在 2.4 和老的内核, 常常有许多 tty ioctl 调用来获得和设置不同的控制线路设置. 这些被常量 TIOCMGET, TIOCMBIS, TIOCMBIC, 和 TIOCMSET 表示. TIOCMGET 用来获得内核的线路设置值, 并且对于 2.6 内核, 这个 ioctl 调用已经被转换为一个 tty 驱动回调函数, 称为 tiocmget. 其他的 3 个 ioctls 已经被简化并且现在用单个的 tty 驱动回调函数所代表, 称为 tiocmset.

tty 驱动中的 iocmget 函数被 tty 核心所调用, 当核心需要知道当前的特定 tty 设备的控制线的物理值. 这常常用来获取一个串口的 DTR 和 RTS 线的值. 如果 tty 驱动不能直接读串口的 MSR 或者 MCR 寄存器, 因为硬件不允许这样, 一个它们的拷贝应当在本地保持. 许多 USB-到-串口 驱动必须实现这类的"影子"变量. 这是这个函数能如何被实现, 任何一个本地的这些值的拷贝被保存:


static int tiny_tiocmget(struct tty_struct *tty, struct file *file)
{
        struct tiny_serial *tiny = tty->driver_ data;
        unsigned int result = 0;
        unsigned int msr = tiny->msr;
        unsigned int mcr = tiny->mcr;
        result = ((mcr & MCR_DTR)  ? TIOCM_DTR  : 0) |  /* DTR is set */
                 ((mcr & MCR_RTS)  ? TIOCM_RTS  : 0) |  /* RTS is set */
                 ((mcr & MCR_LOOP)  ? TIOCM_LOOP : 0) |  /* LOOP 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 函数被 tty 核心调用, 当核心要设置一个特定 tty 设备的控制线值. tty 核心告知 tty 驱动设置什么值和清理什么, 通过传递它们用 2 个变量: set 和 clear. 这些变量包含一个应当改变的线路设置的位掩码. 一个 ioctl 调用从不请求驱动既设置又清理一个特殊的位在同一时间, 因此先发生什么操作没有关系. 这是一个例子, 关于这个函数如何能够由一个 tty 驱动实现:


static int tiny_tiocmset(struct tty_struct *tty, struct file *file, unsigned int set , unsigned int clear)
{
        struct tiny_serial *tiny = tty->driver_data;
        unsigned int mcr = tiny->mcr;
        if (set & TIOCM_RTS)
                mcr |= MCR_RTS;
        if (set & TIOCM_DTR)
                mcr |= MCR_RTS;
        if (clear & TIOCM_RTS)
                mcr &= ~MCR_RTS;
        if (clear & TIOCM_DTR)
                mcr &= ~MCR_RTS;
        /* set the new MCR value in the device */
        tiny->mcr = mcr;
        return 0;
}
上一篇:18.2. tty_drive... 下一篇:18.4. ioctls 函数