- 用户空间设置串口
1.用户空间设置串口参数
用户在使用串口的时候,需要在用户空间设置串口属性。
- 一种是直接通过驱动的ioctl去操作;
- 另一种是使用glibc的库函数来操作,比如常用的tcsetattr()和tcgetattr()函数。
以tcsetattr()为例,该函数定义在glibc的tcsetattr.c中。tcsetattr()的第一个参数为打开的串口设备描述符,第三个参数为要设置的串口新属性,第二个参数为设置操作的模式,TCSANOW表示不等数据传输完成立即改变属性,TCSADRAIN表示等待所有数据传输完成后才改变属性,TCSAFLUSH表示等所有数据传输完成并清空输入输出缓冲区才改变属性。根据不同的模式选择不同的命令,最后调用INLINE_SYSCALL()执行ioctl的系统调用。
2.ioctl 实现
待写
3.tcsetattr实现
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()去处理其它的命令。
2446 static int n_tty_ioctl(struct tty_struct *tty, struct file *file,
2447 unsigned int cmd, unsigned long arg)
2448 {
2449 struct n_tty_data *ldata = tty->disc_data;
2450 int retval;
2451
2452 switch (cmd) {
2453 case TIOCOUTQ:
2454 return put_user(tty_chars_in_buffer(tty), (int __user *) arg);
2455 case TIOCINQ:
2456 down_write(&tty->termios_rwsem);
2457 if (L_ICANON(tty) && !L_EXTPROC(tty))
2458 retval = inq_canon(ldata);
2459 else
2460 retval = read_cnt(ldata);
2461 up_write(&tty->termios_rwsem);
2462 return put_user(retval, (unsigned int __user *) arg);
2463 default:
2464 return n_tty_ioctl_helper(tty, file, cmd, arg);
2465 }
2466 }
894 int n_tty_ioctl_helper(struct tty_struct *tty, struct file *file,
895 unsigned int cmd, unsigned long arg)
896 {
897 int retval;
898
938 default:
939 /* Try the mode commands */
940 return tty_mode_ioctl(tty, file, cmd, arg);
941 }
n_tty_ioctl_helper()处理的命令同样不包含tcsetattr()函数调用的三个命令,所以接着往下看default中的tty_mode_ioctl()函数。在该函数中终于看到对命令TCSETSF/TCSETSW/TCSETS的处理,它们都调用了set_termios()函数。首先把要设置的参数从用户空间拷贝过来,然后再清空驱动的缓存,最后调用 tty_set_termios来完成属性的设置。
708 int tty_mode_ioctl(struct tty_struct *tty, struct file *file,
709 unsigned int cmd, unsigned long arg)
710 {
744 case TCSETSF:
745 return set_termios(real_tty, p, TERMIOS_FLUSH | TERMIOS_WAIT | TERMIOS_OLD);
746 case TCSETSW:
747 return set_termios(real_tty, p, TERMIOS_WAIT | TERMIOS_OLD);
748 case TCSETS:
749 return set_termios(real_tty, p, TERMIOS_OLD);
362 static int set_termios(struct tty_struct *tty, void __user *arg, int opt)
363 {
414 tty_set_termios(tty, &tmp_termios);
420 return 0;
421 }
在tty_set_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,如serial8250_set_termios()完成设置操作,而uart_set_mctrl()最后也是调用uart驱动的set_mctrl()来设置modem的状态。对modem的设置,tty驱动其实有单独提供操作函数,uart_tiocmget()和uart_tiocmset()分别用来获取和设置modem的状态,而这两个函数也是调用uart驱动的get_mctrl()和set_mctrl()来完成的。而在uart驱动中,这几个函数通过直接设置uart端口的寄存器来改变端口的状态。
314 int tty_set_termios(struct tty_struct *tty, struct ktermios *new_termios)
315 {
316 struct ktermios old_termios;
317 struct tty_ldisc *ld;
318
328 down_write(&tty->termios_rwsem);
329 old_termios = tty->termios;
330 tty->termios = *new_termios;
331 unset_locked_termios(tty, &old_termios);
332
333 if (tty->ops->set_termios)
334 tty->ops->set_termios(tty, &old_termios); //serial8250_set_termios
335 else
336 tty_termios_copy_hw(&tty->termios, &old_termios);
337
344 up_write(&tty->termios_rwsem);
345 return 0;
346 }
从整个调用流程可知,在tty驱动框架中,最后的设置函数是set_termios()和tiocmset()/tiocmget(),而这三个函数的具体实现跟终端类型相关,比如上面分析8250/16550驱动是属于串口一类,则调用uart驱动的设置方法。正如LDDR3在讲TTY线路设置中提到的那样,用户的空间的函数调用转换成对ioctl的调用,而多个ioctl的调用再转换成单个set_termios函数的调用。同样,用来获取和设置不同的控制线路设置的iotcl,都转换成对tiocmgeth和tiocmset的调用。
4.Example
#define SERIALTERMINAL "/dev/ttyS0"
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
int set_interface_attribs(int fd, int speed)
{
struct termios tty;
if (tcgetattr(fd, &tty) < 0) {
printf("Error from tcgetattr: %s\n", strerror(errno));
return -1;
}
cfsetospeed(&tty, (speed_t)speed);
cfsetispeed(&tty, (speed_t)speed);
tty.c_cflag |= CLOCAL | CREAD;
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8; /* 8-bit characters */
tty.c_cflag |= PARENB; /* enable parity */
tty.c_cflag &= ~PARODD; /* Even parity */
tty.c_cflag |= CMSPAR; /* force Even parity to SPACE */
tty.c_cflag &= ~CSTOPB; /* only need 1 stop bit */
tty.c_cflag &= ~CRTSCTS; /* no hardware flowcontrol */
tty.c_lflag |= ICANON | ISIG; /* canonical input */
tty.c_lflag &= ~(ECHO | ECHOE | ECHONL | IEXTEN);
tty.c_iflag &= ~IGNCR; /* preserve carriage return */
tty.c_iflag &= ~(INLCR | ICRNL | IUCLC | IMAXBEL);
tty.c_iflag &= ~(IXON | IXOFF | IXANY); /* no SW flowcontrol */
tty.c_iflag |= IGNBRK; /* ignore breaks */
tty.c_iflag &= ~ISTRIP;
tty.c_iflag &= ~IGNPAR; /* report error */
tty.c_iflag |= INPCK; /* test parity */
tty.c_iflag |= PARMRK; /* verbose parity err */
tty.c_oflag &= ~OPOST;
tty.c_cc[VEOL] = 0;
tty.c_cc[VEOL2] = 0;
tty.c_cc[VEOF] = 0x04;
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
printf("Error from tcsetattr: %s\n", strerror(errno));
return -1;
}
return 0;
}
int main(void)
{
char *portname = SERIALTERMINAL;
int fd;
int wlen;
fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC);
if (fd < 0) {
printf("Error opening %s: %s\n", portname, strerror(errno));
return -1;
}
/*baudrate 115200, 8 bits, Space for parity, 1 stop bit */
set_interface_attribs(fd, B115200);
/* simple output */
wlen = write(fd, "Hello!\n", 7);
if (wlen != 7) {
printf("Error from write: %d, %d\n", wlen, errno);
}
tcdrain(fd); /* delay for output */
/* simple canonical input, read lines */
do {
unsigned char buf[81];
unsigned char *p;
int rdlen;
rdlen = read(fd, buf, sizeof(buf) - 1);
if (rdlen > 0) {
buf[rdlen] = 0;
printf("Read %d:", rdlen);
/* first display as hex numbers then ASCII */
for (p = buf; rdlen-- > 0; p++) {
printf(" 0x%x", *p);
if (*p < ' ')
*p = '.'; /* replace any control chars */
}
printf("\n \"%s\"\n\n", buf);
} else if (rdlen < 0) {
printf("Error from read: %d: %s\n", rdlen, strerror(errno));
} else {
/* rdlen == 0 */
printf("Nothing read. EOF?\n");
}
/* repeat read */
} while (1);
}
refer to
- https://developer.aliyun.com/article/578478