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
- Linux tty驱动学习 - 在用户空间设置串口参数操作流程
- Linux tty驱动学习 - UART驱动的write操作流程
- Linux tty驱动学习 - UART驱动的read操作流程
- Linux tty驱动学习 - UART驱动的open操作
- Linux tty驱动学习 - 串口通信,UART及UART驱动概述
- Linux tty驱动学习 - LDD3的tty驱动
- Linux tty驱动学习 - UART驱动注册
- linux内核中串口驱动注册过程(tty驱动)
- 串口 console tty 串口驱动
- linux 串口设置流程
- linux驱动开发-用户open操作在整个系统中的流程
- Linux串口设置参数
- Linux串口设置参数
- Linux串口设置参数
- Linux 下串口驱动头文件之tty.h
- Linux TTY 串口编程
- linux设备驱动,tty串口编程 如何查看linux下串口是否可用?串口名称等
- linux设备驱动,tty串口编程 如何查看linux下串口是否可用?串口名称等
- 在ubuntu server 环境安装 vnc4server 服务和xfce 图形环境
- 常用正则表达式大全
- struts2 拦截器Interceptor中取得request、response
- WCF发布后远程访问的域名解析问题
- Managing Hierarchical Data in MySQL
- Linux tty驱动学习 - 在用户空间设置串口参数操作流程
- 深度学习与自然语言处理之四:卷积神经网络模型(CNN)
- 通过Selector实现 选中、点击文字变色
- FlycoDialog_Master--PopupWindow
- 2016-01-14 FFC
- How to Set Up Replication--MySQL Concept
- JSON
- USACO天梯--Barn Repair
- Hadoop关于处理大量小文件的问题和解决方法