×
单片机 > 单片机程序设计 > 详情

Linux下的串口总线驱动(三)

发布时间:2020-08-31 发布时间:
|
五.线路规程内核代码

底层的物理驱动程序和tty驱动程序负责从硬件上收发数据,而线路规程则负责处理这些数据,并在用户空间和内核空间知觉传递数据。打开串行端口时系统默认的线路规程是N_TTY,它实现终端I/O处理。线路规程也实现通过串行传输协议实现的网络接口,PPP(N_PPP),SLIP(串行线路网际协议)(N_SLIP),红外数据(N_IRDA),蓝牙主机控制接口(N_HCI)。

 

我们在TTY层uart_register_driver函数里初始化termios的时候用到tty_std_termios,这个是线路的原始设置,具体定义如下

struct ktermios tty_std_termios = {     

       .c_iflag = ICRNL | IXON,    //输入标志

       .c_oflag = OPOST | ONLCR,  //输出标志

       .c_cflag = B38400 | CS8 | CREAD | HUPCL,  //控制标志

       .c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK |

                 ECHOCTL | ECHOKE | IEXTEN,  //本地标志

       .c_cc = INIT_C_CC,   //字符控制

       .c_ispeed = 38400,  //输入速率

       .c_ospeed = 38400  //输出速率

};

如果需要对线路原始设置的部分加以修改,则可以添加其他操作。主要分为内核空间修改线路规程和用户空间修改线路规程两个途径。内核空间修改线路规程很简单,只需要对需要修改项进行重新赋值就行了,对于用户空间修改线路规程我们来讲解下。

假如用户空间程序打开和触摸控制器相连的串行端口时,N_TCH将被绑定到底层的串行驱动程序,但假如你想编写程序清空触摸控制器接收的所有原始数据而不处理它,那你就需要修改线路规程为N_TTY并清空所有接收的数据的程序。用户空间修改线程代码如下

fd=open(“/dev/ttys0”,O_RDONLY|O_NOCTTY);

ldisc=N_TTY;

ioctl(fd,TIOCSETD,&ldisc);

 

好了,前面我们从应用角度分析了线路规程的设置,现在我们从理论角度,深度剖析下线路规程是怎么实现的吧。

在TTY层我们讲过TTY层的uart_register_driver和uart_register_port最终调用线路规程的tty_register_driver和tty_register_device。而tty_register_driver和tty_register_device的实现在线路规程中tty_io.c中实现的,我们可以打开tty_io.c这个文件。

首先我们看tty_init函数,在tty_init函数中执行了cdev_init(&tty_cdev, &tty_fops)一行代码,说明向内核中添加了一个cdev设备,我们跟踪tty_fops。

static const struct file_operations tty_fops = {

       .llseek            = no_llseek,

       .read              = tty_read,

       .write             = tty_write,

       .poll        = tty_poll,

       .unlocked_ioctl       = tty_ioctl,

       .compat_ioctl  = tty_compat_ioctl,

       .open             = tty_open,

       .release    = tty_release,

       .fasync           = tty_fasync,

};

这个结构体我们很熟悉,在字符设备中,我们就是使用的这个结构体吧。那说明我们用户进行open,read,write,ioctl等对串口操作时,第一步调用就是这里的open,read,write,ioctl。那么我们就看看怎么由这里的open,read,write,ioctl跟TTY层,UART层的open,read,write,ioctl相联系的。

 

我们就来看看这个open吧

static int __tty_open(struct inode *inode, struct file *filp)

{

       struct tty_struct *tty = NULL;

       int noctty, retval;

       struct tty_driver *driver;

       int index;

       dev_t device = inode->i_rdev;  //获取目标设备的设备号

       unsigned saved_flags = filp->f_flags;

       nonseekable_open(inode, filp);

retry_open:

       noctty = filp->f_flags & O_NOCTTY; 

       index  = -1;

       retval = 0;

       mutex_lock(&tty_mutex);

       if (device == MKDEV(TTYAUX_MAJOR, 0)) { //当前进程的控制终端,/dev/tty

              tty = get_current_tty(); 

              if (!tty) {   //该进程还没有控制终端

                     mutex_unlock(&tty_mutex);

                     return -ENXIO;

              }

              driver = tty_driver_kref_get(tty->driver);  //如果打开的确实是控制终端的处理

              index = tty->index;

              filp->f_flags |= O_NONBLOCK;

              tty_kref_put(tty);

              goto got_driver;

       }

#ifdef CONFIG_VT

       if (device == MKDEV(TTY_MAJOR, 0)) { //当前虚拟控制台,/dev/tty0

              extern struct tty_driver *console_driver;

              driver = tty_driver_kref_get(console_driver);

              index = fg_console; // fg_console表示当前的前台控制台

              noctty = 1;  //因为虚拟控制台原来就打开,故置位

              goto got_driver;

       }

#endif

       if (device == MKDEV(TTYAUX_MAJOR, 1)) { //用于外接的控制台,/dev/console

              struct tty_driver *console_driver = console_device(&index);

              if (console_driver) {

                     driver = tty_driver_kref_get(console_driver);

                     if (driver) {

                           

                            filp->f_flags |= O_NONBLOCK;

                            noctty = 1;

                            goto got_driver;

                     }

              }

              mutex_unlock(&tty_mutex);

              return -ENODEV;

       }

       driver = get_tty_driver(device, &index);

       if (!driver) {

              mutex_unlock(&tty_mutex);

              return -ENODEV;

       }

got_driver:

       if (!tty) {

              //检查我们是否重复打开一个已经存在的tty

              tty = tty_driver_lookup_tty(driver, inode, index);

              if (IS_ERR(tty)) {

                     mutex_unlock(&tty_mutex);

                     return PTR_ERR(tty);

              }

       }

       if (tty) {

              retval = tty_reopen(tty);  //重新打开

              if (retval)

                     tty = ERR_PTR(retval);

       } else

              tty = tty_init_dev(driver, index, 0); //初始化,为需要打开的终端建立tty_struct结构体

       mutex_unlock(&tty_mutex);

       tty_driver_kref_put(driver);

       if (IS_ERR(tty))

              return PTR_ERR(tty);

       filp->private_data = tty;  //设置私有数据

       file_move(filp, &tty->tty_files);

       check_tty_count(tty, "tty_open");

       if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&

           tty->driver->subtype == PTY_TYPE_MASTER)

              noctty = 1;

#ifdef TTY_DEBUG_HANGUP

       printk(KERN_DEBUG "opening %s...", tty->name);

#endif

       if (!retval) {

              if (tty->ops->open)

                     retval = tty->ops->open(tty, filp);   //调用tty_operations下的open函数

              else

                     retval = -ENODEV;

       }

       filp->f_flags = saved_flags;

       if (!retval && test_bit(TTY_EXCLUSIVE, &tty->flags) &&

                                          !capable(CAP_SYS_ADMIN))

              retval = -EBUSY;

       if (retval) {

#ifdef TTY_DEBUG_HANGUP

              printk(KERN_DEBUG "error %d in opening %s...", retval,

                     tty->name);

#endif

              tty_release_dev(filp);

              if (retval != -ERESTARTSYS)

                     return retval;

              if (signal_pending(current))

                     return retval;

              schedule();

              //需要复位f_op,以防挂起

              if (filp->f_op == &hung_up_tty_fops)

                     filp->f_op = &tty_fops;

              goto retry_open;

       }

       mutex_lock(&tty_mutex);

       spin_lock_irq(¤t->sighand->siglock);

       if (!noctty &&

           current->signal->leader &&

           !current->signal->tty &&

           tty->session == NULL)

              __proc_set_tty(current, tty);

       spin_unlock_irq(¤t->sighand->siglock);

       mutex_unlock(&tty_mutex);

       return 0;

}

 

在上面这个open函数中,我们主要涉及为需要打开的终端建立tty_struct结构体而执行的一条代码tty_init_dev(driver, index, 0),同时看到了怎么调用tty_operations下的open函数。在此我们好好看看tty_init_dev(driver, index, 0)的内幕吧。

struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx,

                                                        int first_ok)

{

       struct tty_struct *tty;

       int retval;

    //检查是否pty被多次打开

       if (driver->subtype == PTY_TYPE_MASTER &&

              (driver->flags & TTY_DRIVER_DEVPTS_MEM) && !first_ok)

              return ERR_PTR(-EIO);

       if (!try_module_get(driver->owner))

              return ERR_PTR(-ENODEV);

       tty = alloc_tty_struct();               //分配tty_struct结构体

       if (!tty)

              goto fail_no_mem;

       initialize_tty_struct(tty, driver, idx); //初始化tty_struct结构体

       retval = tty_driver_install_tty(driver, tty);

       if (retval < 0) {

              free_tty_struct(tty);

              module_put(driver->owner);

              return ERR_PTR(retval);

       }

       retval = tty_ldisc_setup(tty, tty->link);  //调用ldisc下open

       if (retval)

              goto release_mem_out;

       return tty;

fail_no_mem:

       module_put(driver->owner);

       return ERR_PTR(-ENOMEM);

release_mem_out:

       if (printk_ratelimit())

              printk(KERN_INFO "tty_init_dev: ldisc open failed, "

                             "clearing slot %d\n", idx);

       release_tty(tty, idx);

       return ERR_PTR(retval);

}

 

我们继续跟踪tty_init_dev中的initialize_tty_struct(tty, driver, idx)函数实现吧

void initialize_tty_struct(struct tty_struct *tty,

              struct tty_driver *driver, int idx)

{

       memset(tty, 0, sizeof(struct tty_struct));

       kref_init(&tty->kref);

       tty->magic = TTY_MAGIC;

       tty_ldisc_init(tty);  // tty_ldisc的初始化,

       tty->session = NULL;

       tty->pgrp = NULL;

       tty->overrun_time = jiffies;

       tty->buf.head = tty->buf.tail = NULL;

       tty_buffer_init(tty);

       mutex_init(&tty->termios_mutex);

       mutex_init(&tty->ldisc_mutex);

       init_waitqueue_head(&tty->write_wait);

       init_waitqueue_head(&tty->read_wait);

       INIT_WORK(&tty->hangup_work, do_tty_hangup);

       mutex_init(&tty->atomic_read_lock);

       mutex_init(&tty->atomic_write_lock);

       mutex_init(&tty->output_lock);

       mutex_init(&tty->echo_lock);

       spin_lock_init(&tty->read_lock);

       spin_lock_init(&tty->ctrl_lock);

       INIT_LIST_HEAD(&tty->tty_files);

       INIT_WORK(&tty->SAK_work, do_SAK_work);

       tty->driver = driver;

       tty->ops = driver->ops;

       tty->index = idx;

       tty_line_name(driver, idx, tty->name);

}

我们继续跟踪initialize_tty_struct函数中的tty_ldisc_init(tty)函数

void tty_ldisc_init(struct tty_struct *tty)

{

       struct tty_ldisc *ld = tty_ldisc_get(N_TTY);  //设置线路规程N_TTY

       if (IS_ERR(ld))

              panic("n_tty: init_tty");

       tty_ldisc_assign(tty, ld);

}

在tty_ldisc_init里,我们终于找到了N_TTY,这是默认的线路规程。

继续看tty_init_dev,我们发现retval = tty_ldisc_setup(tty, tty->link);继续跟踪

int tty_ldisc_setup(struct tty_struct *tty, struct tty_struct *o_tty)

{

       struct tty_ldisc *ld = tty->ldisc;

       int retval;

       retval = tty_ldisc_open(tty, ld);

       if (retval)

              return retval;

       if (o_tty) {

              retval = tty_ldisc_open(o_tty, o_tty->ldisc);

              if (retval) {

                     tty_ldisc_close(tty, ld);

                     return retval;

              }

              tty_ldisc_enable(o_tty);

       }

       tty_ldisc_enable(tty);

       return 0;

}

然后我们跟踪tty_ldisc_setup函数中的tty_ldisc_open函数

static int tty_ldisc_open(struct tty_struct *tty, struct tty_ldisc *ld)

{

       WARN_ON(test_and_set_bit(TTY_LDISC_OPEN, &tty->flags));

       if (ld->ops->open)

              return ld->ops->open(tty); //打开ldisc下的open,进行链路的初始化

       return 0;

}

 

在tty_ldisc_open这里已经通过相应tty_ldisc结构所提供的函数指针调用了与链路规则有关的open操作。

前面tty_open函数中也有个open调用,这是为什么呢?因为具体的终端类型也可能有需要在打开文件时加以调用的函数。对于用作控制台的虚拟终端,其tty_driver数据结构为console_driver,其open函数则为con_open()。

综上,我们可以把tty_io.c看作是tty核心,然后tty核心里调用ldisc中的open,ldisc里的open调用tty层的open,tty层的open调用uart层的open,最终实现打开操作。

 

最后再次总结如下几点:

其一,内核中有一个链表tty_drivers,系统在初始化时,或者安装某种终端设备的驱动模块时,通过函数tty_register_driver()将各种终端设备的tty_driver结构登记到这个链表中。每当新打开一个终端设备时,就要根据其设备号通过函数get_tty_driver()在这个链表中找到的tty_driver结构,并把它复制到具体的tty_struct结构体中。

其二,当新创建一个tty_struct结构时,就把相应的tty_ldisc结构复制到tty_struct结构体中的这个成员中。

其三,另外内核中的一个重要指针termios,这个数据结构在某种程度上可以看作是对tty_ldisc结构的补充,它规定了对接口上输入和输出的每个字符所作的处理以及传输的速度,即波特率。




『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
AVR熔丝位操作时的要点和需要注意的相关事项