Linux tty駆動学習-ユーザ空間にシリアルポートパラメータを設定する操作フロー


ユーザがシリアルポートを使用する場合、ユーザ空間にシリアルポート属性を設定する必要があります.1つは、駆動するioctlによって直接操作することですが、glibcのライブラリ関数を使用して操作するのが一般的です.例えば、一般的なtcsetattr()とtcgetattr()関数です.tcsetattr()を例にとる、glibcのtcsetattrを定義する.c中.tcsetattr()の1番目のパラメータはオープンシリアルデバイス記述子であり、3番目のパラメータは設定するシリアルポートの新しい属性であり、2番目のパラメータは設定操作のモードであり、TCSANOWは不等なデータ転送が完了するとすぐに属性を変更し、TCSADRAINはすべてのデータ転送が完了するのを待ってから属性を変更することを示す.TCSAFLUSHは、すべてのデータ転送が完了し、入出力バッファが空になってから属性が変更されることを示します.モードによって異なるコマンドを選択し、最後にINLINE_を呼び出します.SYSCALL()はioctlのシステム呼び出しを実行します.
int tcsetattr (fd, optional_actions, termios_p)
     int fd;
     int optional_actions;
     const struct termios *termios_p;
{
  struct __kernel_termios k_termios;
  unsigned long int cmd;

  switch (optional_actions)
    {
    case TCSANOW:
      cmd = TCSETS;
      break;
    case TCSADRAIN:
      cmd = TCSETSW;
      break;
    case TCSAFLUSH:
      cmd = TCSETSF;
      break;
    default:
      __set_errno (EINVAL);
      return -1;
    }

  k_termios.c_iflag = termios_p->c_iflag & ~IBAUD0;
  k_termios.c_oflag = termios_p->c_oflag;
  k_termios.c_cflag = termios_p->c_cflag;
  k_termios.c_lflag = termios_p->c_lflag;
  k_termios.c_line = termios_p->c_line;
#if defined _HAVE_C_ISPEED && defined _HAVE_STRUCT_TERMIOS_C_ISPEED
  k_termios.c_ispeed = termios_p->c_ispeed;
#endif
#if defined _HAVE_C_OSPEED && defined _HAVE_STRUCT_TERMIOS_C_OSPEED
  k_termios.c_ospeed = termios_p->c_ospeed;
#endif
  memcpy (&k_termios.c_cc[0], &termios_p->c_cc[0],
	  __KERNEL_NCCS * sizeof (cc_t));

  return INLINE_SYSCALL (ioctl, 3, fd, cmd, &k_termios);
}

ioctlシステム呼び出しはttyコア層に実行され、まずtty_が呼び出されるioctl()関数.この関数ではTCSETS/TSCSETSW/TSCSETSFの3つのコマンドを直接処理していないので、tty->ops->ioctl、すなわちtty駆動のiotcl関数を呼び出して処理します.tty駆動ioctl関数はuart_ioctl()は、この関数でも上記3つのコマンドを処理していないので、uart駆動uport->ops->ioctl()関数を呼び出して処理します.8250/16550のドライバはioctl操作関数を定義していないのでtty_に戻ります.ioctl()では、n_tty.cではn_と定義されるtty_ioctl().この関数自体はTIOCOUTQ/TIIOCINQの2つのコマンドのみを処理しますが、defaultではn_が呼び出されます.tty_ioctl_helper()は他のコマンドを処理します.
static int n_tty_ioctl(struct tty_struct *tty, struct file *file,
		       unsigned int cmd, unsigned long arg)
{
	int retval;

	switch (cmd) {
	case TIOCOUTQ:
		return put_user(tty_chars_in_buffer(tty), (int __user *) arg);
	case TIOCINQ:
		/* FIXME: Locking */
		retval = tty->read_cnt;
		if (L_ICANON(tty))
			retval = inq_canon(tty);
		return put_user(retval, (unsigned int __user *) arg);
	default:
		return n_tty_ioctl_helper(tty, file, cmd, arg);
	}
}

n_tty_ioctl_helper()が処理するコマンドには、tcsetattr()関数呼び出しの3つのコマンドは含まれていないので、defaultのtty_を下に見ます.mode_ioctl()関数.この関数では、コマンドTCSETSF/TSCSETSW/TSCSETSの処理がやっと見られ、set_が呼び出されました.termios()関数.まず設定するパラメータをユーザ空間からコピーしてから、駆動するキャッシュを空にしてchange_を呼び出します.termios()でプロパティの設定を完了します.
static int set_termios(struct tty_struct *tty, void __user *arg, int opt)
{
	struct ktermios tmp_termios;
	struct tty_ldisc *ld;
	int retval = tty_check_change(tty);

	if (retval)
		return retval;

	mutex_lock(&tty->termios_mutex);
	memcpy(&tmp_termios, tty->termios, sizeof(struct ktermios));
	mutex_unlock(&tty->termios_mutex);

	if (opt & TERMIOS_TERMIO) {
		if (user_termio_to_kernel_termios(&tmp_termios,
						(struct termio __user *)arg))
			return -EFAULT;
#ifdef TCGETS2
	} else if (opt & TERMIOS_OLD) {
		if (user_termios_to_kernel_termios_1(&tmp_termios,
						(struct termios __user *)arg))
			return -EFAULT;
	} else {
		if (user_termios_to_kernel_termios(&tmp_termios,
						(struct termios2 __user *)arg))
			return -EFAULT;
	}
#else
	} else if (user_termios_to_kernel_termios(&tmp_termios,
					(struct termios __user *)arg))
		return -EFAULT;
#endif

	/* If old style Bfoo values are used then load c_ispeed/c_ospeed
	 * with the real speed so its unconditionally usable */
	tmp_termios.c_ispeed = tty_termios_input_baud_rate(&tmp_termios);
	tmp_termios.c_ospeed = tty_termios_baud_rate(&tmp_termios);

	ld = tty_ldisc_ref(tty);

	if (ld != NULL) {
		if ((opt & TERMIOS_FLUSH) && ld->ops->flush_buffer)
			ld->ops->flush_buffer(tty);
		tty_ldisc_deref(ld);
	}

	if (opt & TERMIOS_WAIT) {
		tty_wait_until_sent(tty, 0);
		if (signal_pending(current))
			return -EINTR;
	}

	change_termios(tty, &tmp_termios);

	/* FIXME: Arguably if tmp_termios == tty->termios AND the
	   actual requested termios was not tmp_termios then we may
	   want to return an error as no user requested change has
	   succeeded */
	return 0;
}

change_termios()で古いパラメータ設定をold_に保存しますtermiosで新しい設定をtty_に保存しますstructでは、最後にtty駆動のset_を呼び出すtermios()関数.serial_core.cではuart_として定義されるset_termios()は,この関数がまず新しい設定にパラメータの変動があるかどうかを判断し,何の変化もしなければ直接返す.そしてuart_をそれぞれ呼び出すchange_speed()とuart_set_mctrl()でパラメータを設定します.uart_change_speed()はuartドライバが呼び出されたset_であるtermios()は設定操作を完了し、uart_set_mctrl()は最後にuartドライバを呼び出すset_でもあるmctrl()でmodemの状態を設定します.modemの設定に対して、tty駆動は実際には単独で操作関数を提供して、uart_tiocmget()とuart_tiocmset()はそれぞれmodemの状態を取得および設定するために使用され、この2つの関数もuartドライバを呼び出すget_である.mctrl()とset_mctrl()で完成しました.uart駆動では、これらの関数はuartポートのレジスタを直接設定することによってポートの状態を変化させる.
static void change_termios(struct tty_struct *tty, struct ktermios *new_termios)
{
	struct ktermios old_termios;
	struct tty_ldisc *ld;
	unsigned long flags;

	/*
	 *	Perform the actual termios internal changes under lock.
	 */


	/* FIXME: we need to decide on some locking/ordering semantics
	   for the set_termios notification eventually */
	mutex_lock(&tty->termios_mutex);
	old_termios = *tty->termios;
	*tty->termios = *new_termios;
	unset_locked_termios(tty->termios, &old_termios, tty->termios_locked);

	/* See if packet mode change of state. */
	if (tty->link && tty->link->packet) {
		int old_flow = ((old_termios.c_iflag & IXON) &&
				(old_termios.c_cc[VSTOP] == '\023') &&
				(old_termios.c_cc[VSTART] == '\021'));
		int new_flow = (I_IXON(tty) &&
				STOP_CHAR(tty) == '\023' &&
				START_CHAR(tty) == '\021');
		if (old_flow != new_flow) {
			spin_lock_irqsave(&tty->ctrl_lock, flags);
			tty->ctrl_status &= ~(TIOCPKT_DOSTOP | TIOCPKT_NOSTOP);
			if (new_flow)
				tty->ctrl_status |= TIOCPKT_DOSTOP;
			else
				tty->ctrl_status |= TIOCPKT_NOSTOP;
			spin_unlock_irqrestore(&tty->ctrl_lock, flags);
			wake_up_interruptible(&tty->link->read_wait);
		}
	}

	if (tty->ops->set_termios)
		(*tty->ops->set_termios)(tty, &old_termios);
	else
		tty_termios_copy_hw(tty->termios, &old_termios);

	ld = tty_ldisc_ref(tty);
	if (ld != NULL) {
		if (ld->ops->set_termios)
			(ld->ops->set_termios)(tty, &old_termios);
		tty_ldisc_deref(ld);
	}
	mutex_unlock(&tty->termios_mutex);
}
static const struct tty_operations uart_ops = {
	……
	.ioctl		= uart_ioctl,
	……
	.set_termios	= uart_set_termios,
	……
	.tiocmget	= uart_tiocmget,
	.tiocmset	= uart_tiocmset,
	……
};
static void uart_set_termios(struct tty_struct *tty, struct ktermios *old_termios)
{
	struct uart_state *state = tty->driver_data;
	unsigned long flags;
	unsigned int cflag = tty->termios->c_cflag;

#define RELEVANT_IFLAG(iflag)	((iflag) & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))
	if ((cflag ^ old_termios->c_cflag) == 0 &&
	    tty->termios->c_ospeed == old_termios->c_ospeed &&
	    tty->termios->c_ispeed == old_termios->c_ispeed &&
	    RELEVANT_IFLAG(tty->termios->c_iflag ^ old_termios->c_iflag) == 0) {
		return;
	}

	uart_change_speed(state, old_termios);

	/* Handle transition to B0 status */
	if ((old_termios->c_cflag & CBAUD) && !(cflag & CBAUD))
		uart_clear_mctrl(state->uart_port, TIOCM_RTS | TIOCM_DTR);
	/* Handle transition away from B0 status */
	else if (!(old_termios->c_cflag & CBAUD) && (cflag & CBAUD)) {
		unsigned int mask = TIOCM_DTR;
		if (!(cflag & CRTSCTS) ||
		    !test_bit(TTY_THROTTLED, &tty->flags))
			mask |= TIOCM_RTS;
		uart_set_mctrl(state->uart_port, mask);
	}

	/* Handle turning off CRTSCTS */
	if ((old_termios->c_cflag & CRTSCTS) && !(cflag & CRTSCTS)) {
		spin_lock_irqsave(&state->uart_port->lock, flags);
		tty->hw_stopped = 0;
		__uart_start(tty);
		spin_unlock_irqrestore(&state->uart_port->lock, flags);
	}
	/* Handle turning on CRTSCTS */
	else if (!(old_termios->c_cflag & CRTSCTS) && (cflag & CRTSCTS)) {
		spin_lock_irqsave(&state->uart_port->lock, flags);
		if (!(state->uart_port->ops->get_mctrl(state->uart_port) & TIOCM_CTS)) {
			tty->hw_stopped = 1;
			state->uart_port->ops->stop_tx(state->uart_port);
		}
		spin_unlock_irqrestore(&state->uart_port->lock, flags);
	}

}
static struct uart_ops serial8250_pops = {
	……
	.set_mctrl	= serial8250_set_mctrl,
	.get_mctrl	= serial8250_get_mctrl,
	……
	.set_termios	= serial8250_set_termios,
	……
};

呼び出しプロセス全体からtty駆動フレームワークでは、最後の設定関数はset_であることがわかるtermios()およびtiocmset()/tiocmget()の3つの関数の具体的な実装は、8250/16550ドライバがシリアルポートのクラスに属すると分析されたように、uartドライバの設定方法を呼び出す端末タイプに関連する.LDDR 3がTTY回線設定に言及したように、ユーザの空間の関数呼び出しはioctlの呼び出しに変換され、複数のioctlの呼び出しは単一set_に変換される.termios関数の呼び出し.同様に、異なる制御回線設定を取得および設定するために使用されるiotclは、tiocmgethおよびtiocmsetの呼び出しに変換される.