Linux tty驱动学习 - 在用户空间设置串口参数操作流程

来源:互联网 发布:网络教育入学考试科目 编辑:程序博客网 时间:2024/05/23 20:25

用户在使用串口的时候,需要在用户空间设置串口属性,一种是直接通过驱动的ioctl去操作,但常用的方法是使用glibc的库函数来操作,比如常用的tcsetattr()和tcgetattr()函数。以tcsetattr()为例,该函数定义在glibc的tcsetattr.c中。tcsetattr()的第一个参数为打开的串口设备描述符,第三个参数为要设置的串口新属性,第二个参数为设置操作的模式,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/TCSETSW/TCSETSF三个命令,所以会再调用tty->ops->ioctl,即调用tty驱动的iotcl函数来处理。tty驱动的ioctl函数为uart_ioctl(),在该函数中也没有对上述三个命令进行处理,所以接着调用uart驱动uport->ops->ioctl()函数来处理。而8250/16550的驱动并没有定义ioctl操作函数,所以回到tty_ioctl()中继续调用线路规程的ld->ops->ioctl()函数来处理,该函数在n_tty.c中被定义为n_tty_ioctl()。该函数本身只处理TIOCOUTQ/TIOCINQ两个命令,但在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()函数调用的三个命令,所以接着往下看default中的tty_mode_ioctl()函数。在该函数中终于看到了对命令TCSETSF/TCSETSW/TCSETS的处理,它们都调用了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的状态,而这两个函数也是调用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;elsetty->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);elsetty_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 = {<span style="white-space:pre"></span>…….ioctl= uart_ioctl,<span style="white-space:pre"></span>…….set_termios= uart_set_termios,<span style="white-space:pre"></span>…….tiocmget= uart_tiocmget,.tiocmset= uart_tiocmset,<span style="white-space:pre"></span>……};

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 = {<span style="white-space:pre"></span>…….set_mctrl= serial8250_set_mctrl,.get_mctrl= serial8250_get_mctrl,<span style="white-space:pre"></span>…….set_termios= serial8250_set_termios,<span style="white-space:pre"></span>……};

从整个调用流程可知,在tty驱动框架中,最后的设置函数是set_termios()和tiocmset()/tiocmget(),而这三个函数的具体实现跟终端类型相关,比如上面分析8250/16550驱动是属于串口一类,则调用uart驱动的设置方法。正如LDDR3在讲TTY线路设置中提到的那样,用户的空间的函数调用转换成对ioctl的调用,而多个ioctl的调用再转换成单个set_termios函数的调用。同样,用来获取和设置不同的控制线路设置的iotcl,都转换成对tiocmgeth和tiocmset的调用。
1 0