tty_driver-set_termios

1. 设置 tty 参数

设置 tty 参数时会设置 tty_structdisc_data 的一些成员变量:

struct n_tty_data {
	...
		/* must hold exclusive termios_rwsem to reset these */
	unsigned char lnext:1, erasing:1, raw:1, real_raw:1, icanon:1;
	unsigned char push:1;
	...
}

在内核源码目录执行 grep -nr 'ldata->real_raw' drivers/* 命令,结果显示只有 drivers/tty/n_tty.cn_tty_set_termios 函数对 real_raw 进行了修改。
也就是说,修改 tty 参数的操作只能通过 n_tty_set_termios 函数完成。

1.1. n_tty_set_termios

函数接收两个参数:第一个是要设置参数的 tty ,第二个是该 tty 的原参数。
tty_structdisc_data 保存 line discipline 相关的数据, 即 n_tty_set_termios 函数的操作对象。

static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old)
{
	struct n_tty_data *ldata = tty->disc_data;

    /*
     * 1. old = NULL , 没有旧参数 —— 初始化 ldata
     * 2. (old->clflag ^ tty->termios.c_lflag) ,新旧参数之间的差异
     *    (ICANON | EXTPROC) , canonical 模式或 LINEMODE ( man stty )
     *    发生 canonical 模式或 LINEMODE 的切换  
     */
	if (!old || (old->c_lflag ^ tty->termios.c_lflag) & (ICANON | EXTPROC)) {

        // 重置 read_flags 为 0 ,行起始位置指向 read_tail
		bitmap_zero(ldata->read_flags, N_TTY_BUF_SIZE);
		ldata->line_start = ldata->read_tail;

        /*
         * 1. tty 处于 noncanonical 模式 —— 设置 canon_head 为 read_tail
         * 2. ldata 中没有数据
         */
		if (!L_ICANON(tty) || !read_cnt(ldata)) {
			ldata->canon_head = ldata->read_tail;
			ldata->push = 0;
		} else {    // tty 处于 canonical 模式,并且 ldata 有数据

            // 置位 read_flags 中的 read_head 前一字节,作为 buf 的结束标志
			set_bit((ldata->read_head - 1) & (N_TTY_BUF_SIZE - 1),
				ldata->read_flags);

            // canon_head 指向 read_head
			ldata->canon_head = ldata->read_head;

            // push 的含义未知
			ldata->push = 1;
		}
		ldata->commit_head = ldata->read_head;
		ldata->erasing = 0;
		ldata->lnext = 0;
	}

    // 设置 icanon 标志,表明是否工作在 canonical 模式
	ldata->icanon = (L_ICANON(tty) != 0);

    // 下列输入参数都需要对输入数据进行处理
	if (I_ISTRIP(tty) || I_IUCLC(tty) || I_IGNCR(tty) ||
	    I_ICRNL(tty) || I_INLCR(tty) || L_ICANON(tty) ||
	    I_IXON(tty) || L_ISIG(tty) || L_ECHO(tty) ||
	    I_PARMRK(tty)) {

        /*
         * char_map 对应 256 个 ASCII 字符,
         * 如果 char_map 中对应的字符置位,表明需要对这些特殊字符进行处理。
         * 下面的 if 语句根据设置的参数设置 char_map 中对应的字符标志位。
         */
		bitmap_zero(ldata->char_map, 256);

		if (I_IGNCR(tty) || I_ICRNL(tty))
			set_bit('\r', ldata->char_map);
		if (I_INLCR(tty))
			set_bit('\n', ldata->char_map);

		if (L_ICANON(tty)) {
			set_bit(ERASE_CHAR(tty), ldata->char_map);
			set_bit(KILL_CHAR(tty), ldata->char_map);
			set_bit(EOF_CHAR(tty), ldata->char_map);
			set_bit('\n', ldata->char_map);
			set_bit(EOL_CHAR(tty), ldata->char_map);
			if (L_IEXTEN(tty)) {
				set_bit(WERASE_CHAR(tty), ldata->char_map);
				set_bit(LNEXT_CHAR(tty), ldata->char_map);
				set_bit(EOL2_CHAR(tty), ldata->char_map);
				if (L_ECHO(tty))
					set_bit(REPRINT_CHAR(tty),
						ldata->char_map);
			}
		}
		if (I_IXON(tty)) {
			set_bit(START_CHAR(tty), ldata->char_map);
			set_bit(STOP_CHAR(tty), ldata->char_map);
		}
		if (L_ISIG(tty)) {
			set_bit(INTR_CHAR(tty), ldata->char_map);
			set_bit(QUIT_CHAR(tty), ldata->char_map);
			set_bit(SUSP_CHAR(tty), ldata->char_map);
		}
		clear_bit(__DISABLED_CHAR, ldata->char_map);
		ldata->raw = 0;
		ldata->real_raw = 0;
	} else {

        /*
         * stty -F /dev/ttyS0 raw 会设置 ldata->raw = 1
         * 针对 serial tty 驱动, tty->driver->flags 设置了
         * TTY_DRIVER_REAL_RAW 标志,因此 ldata->real_raw = 1
         */
		ldata->raw = 1;
		if ((I_IGNBRK(tty) || (!I_BRKINT(tty) && !I_PARMRK(tty))) &&
		    (I_IGNPAR(tty) || !I_INPCK(tty)) &&
		    (tty->driver->flags & TTY_DRIVER_REAL_RAW))
			ldata->real_raw = 1;
		else
			ldata->real_raw = 0;
	}
	/*
	 * Fix tty hang when I_IXON(tty) is cleared, but the tty
	 * been stopped by STOP_CHAR(tty) before it.
	 */
    // 之前开启了 IXON ,当前没有开启,并且 tty 被 IXON 停止了
	if (!I_IXON(tty) && old && (old->c_iflag & IXON) && !tty->flow_stopped) {

        /*
         * start_tty -> __start_tty -> uart_start
         *                             tty_wakeup
         * tty_wakeup 调用 wake_up_interrutible_poll 唤醒
         * tty->write_wait 等待队列上的进程
         */
		start_tty(tty);
		process_echoes(tty);
	}

	/* The termios change make the tty ready for I/O */
    // 唤醒 write_wait 和 read_wait 上的进程
	wake_up_interruptible(&tty->write_wait);
	wake_up_interruptible(&tty->read_wait);
}

n_tty_set_termios 函数的引用有 n_tty_open ,两个函数都是 n_tty_ops 的成员,即

static struct tty_ldisc_ops n_tty_ops = {
    ...
    .open = n_tty_open,
    .set_termios = n_tty_set_termios,
    ...
};

在内核源码下执行 grep -nr 'ld->ops->set_termios' drivers/tty/* ,从输出结果看,直接调用 set_termios 函数的有 tty_set_termios

1.2. n_tty_open

n_tty_open 调用 n_tty_set_termios 时,第二个参数为 NULL ,即旧的 tty 参数不存在。

n_tty_open 只有 tty_ldisc_open 会调用,调用后者的函数有很多,都位于 drivers/tty/tty_ldisc.c 中。
其中较为常用的函数为 tty_ldisc_reinittty_ldisc_setuptty_set_ldisc

三个函数的引用分别为:

  1. tty_ldisc_reinittty_reopen 执行时, tty 没有 ldisc 时调用;还有 tty_ldisc_hangup 传入的参数 reinit 为 1 时调用。
  2. tty_ldisc_setuptty_init_dev 中调用。
  3. tty_set_disctiocsetd 中调用,后者用于实现 tty_ioctl
posted @ 2020-08-07 11:22  glob  阅读(852)  评论(0编辑  收藏  举报