linux serial framework (3) - serial example

  • 用户空间设置串口

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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值