Linux 设备驱动 (第三版)第 18 章 TTY 驱动18.4. ioctls 函数

18.4. ioctls 函数

18.4. ioctls 函数

在 struct tty_driver 中的 ioctl 函数被 tty 核心调用当 ioctl(2) 被在设备节点上调用. 如果这个 tty 驱动不知道如何处理传递给它的 ioctl 值, 它应当返回 -ENOIOCTLCMD 来试图让 tty 核心实现一个通用的调用版本.

2.6 内核定义了大约 70 个不同的 tty ioctls, 可被用来发送给一个 tty 驱动. 大部分的 tty 驱动不处理它们全部, 但是只有一个小的更普通的子集. 这是一个更通用的 tty ioctls 列表, 它们的含义, 以及如何实现它们:

TIOCSERGETLSR
获得这个 tty 设备的线路状态寄存器( LSR )的值.

TIOCGSERIAL
获得串口线信息. 调用者可以潜在地从 tty 设备获得许多串口线路信息, 在这个调用中一次全部. 一些程序( 例如 setserial 和 dip) 调用这个函数来确保波特率被正确设置, 以及来获得通常的关于驱动控制的设备类型信息. 调用者传递一个指向一个大的 serial_struct 结构的指针, 这个结构应当由 tty 驱动填充正确的值. 这是一个如何实现这个的例子:


static int tiny_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg)
{
        struct tiny_serial *tiny = tty->driver_data;
        if (cmd == TIOCGSERIAL)
        {
                struct serial_struct tmp;
                if (!arg)
                        return -EFAULT;
                memset(&tmp, 0, sizeof(tmp));
                tmp.type  = tiny->serial.type;
                tmp.line  = tiny->serial.line;
                tmp.port  = tiny->serial.port;
                tmp.irq  = tiny->serial.irq;
                tmp.flags  = ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ;
                tmp.xmit_fifo_size = tiny->serial.xmit_fifo_size;
                tmp.baud_base = tiny->serial.baud_base;
                tmp.close_delay = 5*HZ;
                tmp.closing_wait = 30*HZ;
                tmp.custom_divisor = tiny->serial.custom_divisor;
                tmp.hub6 = tiny->serial.hub6;
                tmp.io_type = tiny->serial.io_type;
                if (copy_to_user((void __user *)arg, &tmp, sizeof(tmp)))
                        return -EFAULT;
                return 0;
        }
        return -ENOIOCTLCMD;
}

TIOCSSERIAL
设置串口线路信息. 这是 IOCGSERIAL 的反面, 并且允许用户一次全部设置 tty 设备的串口线状态. 一个指向 struct serial_struct 的指针被传递给这个调用, 填满这个 tty 设备应当被设置的数据. 如果这个 tty 驱动没有实现这个调用, 大部分程序仍然正确工作.

TIOCMIWAIT
等待 MSR 改变. 用户在非寻常的情况下请求这个 ioctl, 它想在内核中睡眠直到这个 tty 设备的 MSR 寄存器发生某些事情. arg 参数包含用户在等待的事件类型. 这通常用来等待直到一个状态线变化, 指示有更多的数据发送给设备.

当实现这个 ioctl 时要小心, 并且不要使用 interruptible_sleep_on 调用, 因为它是不安全的(有很多不好的竞争条件涉及它). 相反, 一个 wait_queue 应当用来避免这个问题. 这是一个如何实现这个 ioctl 的例子:


static int tiny_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg)
{
        struct tiny_serial *tiny = tty->driver_data;
        if (cmd == TIOCMIWAIT)
        {
                DECLARE_WAITQUEUE(wait, current);
                struct async_icount cnow;
                struct async_icount cprev;
                cprev = tiny->icount;
                while (1) {
                        add_wait_queue(&tiny->wait, &wait);
                        set_current_state(TASK_INTERRUPTIBLE);
                        schedule();
                        remove_wait_queue(&tiny->wait, &wait); /* see if a signal woke us up */
                        if (signal_pending(current))
                                return -ERESTARTSYS;
                        cnow = tiny->icount;
                        if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr &&
                                        cnow.dcd == cprev.dcd && cnow.cts == cprev.cts)
                                return -EIO; /* no change => error */
                        if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) || ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) || ((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) || ((arg & TIOCM_CTS) && (cnow.cts != cprev.cts)) ) {
                                return 0;
                        }
                        cprev = cnow;
                }
        }
        return -ENOIOCTLCMD;
}

在 tty 驱动的代码中能知道 MSR 寄存器改变的某些地方, 下面的代码行必须调用以便这个代码能正常工作:


wake_up_interruptible(&tp->wait);

TIOCGICOUNT
获得中断计数. 当用户要知道已经产生多少串口线中断时调用. 如果驱动有一个中断处理, 它应当定义一个内部计数器结构来跟踪这些统计和递增适当的计数器, 每次这个函数被内核运行时.

这个 ioctl 调用传递内核一个指向结构 serial_icounter_struct 的指针, 它应当被 tty 驱动填充. 这个调用常常和之前的 IOCMIWAIT ioctl 调用结合使用. 如果 tty 驱动跟踪所有的这些中断在驱动操作时, 实现这个调用的代码会非常简单:


static int tiny_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg)
{
        struct tiny_serial *tiny = tty->driver_data;
        if (cmd == TIOCGICOUNT)
        {
                struct async_icount cnow = tiny->icount;
                struct serial_icounter_struct icount;
                icount.cts = cnow.cts;
                icount.dsr = cnow.dsr;
                icount.rng = cnow.rng;
                icount.dcd = cnow.dcd;
                icount.rx = cnow.rx;
                icount.tx = cnow.tx;
                icount.frame = cnow.frame;
                icount.overrun = cnow.overrun;
                icount.parity = cnow.parity;
                icount.brk = cnow.brk;
                icount.buf_overrun = cnow.buf_overrun;
                if (copy_to_user((void __user *)arg, &icount, sizeof(icount)))
                        return -EFAULT;
                return 0;
        }
        return -ENOIOCTLCMD;
}