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

发布者:算法之手最新更新时间:2016-03-08 来源: eefocus关键字:Linux  串口  总线驱动 手机看文章 扫描二维码
随时随地手机看文章
五.线路规程内核代码

底层的物理驱动程序和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结构的补充,它规定了对接口上输入和输出的每个字符所作的处理以及传输的速度,即波特率。

关键字:Linux  串口  总线驱动 引用地址:Linux下的串口总线驱动(三)

上一篇:Linux下的串口总线驱动(二)
下一篇:Linux下的串口总线驱动(四)

推荐阅读最新更新时间:2024-03-16 14:46

单片机串口通信UART与USART的区别
UART与USART都是单片机上的串口通信,他们之间的区别如下: 首先从名字上看: UART:universal asynchronous receiver and transmitter通用异步收/发器 USART:universal synchronous asynchronous receiver and transmitter通用同步/异步收/发器 从名字上可以看出,USART在UART基础上增加了同步功能,即USART是UART的增强型,事实也确实是这样。但是具体增强到了什么地方呢? 其实当我们使用USART在异步通信的时候,它与UART没有什么区别,但是用在同步通信的时候,区别就很明显了:大家都知道
[单片机]
Linux驱动曝光AMD Vega20核心:有望对应7nm加速卡
  这两天发硬件新闻总市心有戚戚,即便看到挺靠谱的消息,也总是有一种愚人节埋雷的隐忧。不过,下面这则,笔者仔细查验了一番,应该可信。下面就随嵌入式小编一起来了解一下相关内容吧。   据VideoCardz,在 Linux 的Freedesktop分支中,出现了Vega20的芯片识别代码。   Vega20识别出来了6种型号,此前Vega 10在驱动中识别出9种,覆盖Instinct、Radeon Pro和RX系列产品。     不过,按照 AMD 的规划,GPU产品线在2018年分为三个级别,其中高端桌面的Vega 56/64已经与我们见面,Vega Mobile已经用在APU和Intel的Kaby Lake-G中。而唯一称
[嵌入式]
再造STM32---第十七部分:USART—串口通讯
本章参考资料:《STM32F4xx 中文参考手册》 USART 章节。 学习本章时,配合《STM32F4xx 中文参考手册》 USART 章节一起阅读,效果会更佳,特别是涉及到寄存器说明的部分。 特别说明, 本书内容是以 STM32F42xxx 系列控制器资源讲解。 17.1 串口通讯协议简介: 物理层: 规定通讯系统中具有机械、电子功能部分的特性,确保原始数据在物理媒体的传输。其实就是硬 件部分。 协议层: 协议层主要规定通讯逻辑,统一收发双方的数据打包、解包标准。其实就是软件部分。 1-RS232标准: RS232标准串口通讯结构图: 1、 RS232标准串口主要用于工业设备直接通信 2、电平转换芯片一般有M
[单片机]
再造STM32---第十七部分:USART—<font color='red'>串口</font>通讯
#51单片机#蓝牙模块(ATKSPPHC06从机串口)的使用方法
#include AT89X51.H #include intrins.h // 函数原形定义 #define uchar unsigned char #define uint unsigned int void main (void); // 主函数 void LED_OUT(uchar X); // LED单字节串行移位函数 void LED_print (uchar p, uchar x) ; unsigned char code LED_0F ; // LED字模表 sbit DIO = P1^0; //串行数据输入 sbit RCLK = P1^1; //时钟脉冲信号 上升沿有效 sbit SCLK =
[单片机]
#51单片机#蓝牙模块(ATKSPPHC06从机<font color='red'>串口</font>)的使用方法
TI MSP430系列单片机串口通信波特率计算方法
TI MSP430系列单片机,usart模块的波特率值设定是通过以下三个寄存器决定的:UxBR0,UxBR1,UxMCTL 波特率=BRCLK/N ,主要是计算出N。 BRCLK:时钟源,可以通过寄存器设定何为时钟源; 通过寄存器UCAxCTL1的SSEL两位选择,01:ACLK,02:SMCLK N:波特率产生的分频因子。N=UxBR1+UxBR0+UxMCTL,其中UxBR1+UxBR0为整数部分,UxMCTL为设定小数部分,其中 UxBR1为高位,UxBR0为低位,两者结合起来为一个16位的字。 举例说明:波特率=115200,时钟源=8MHz ,为外部晶体振荡器 N=8000000/115200=69.44
[单片机]
嵌入式linux在工业控制领域中的应用
摘要:针对嵌入网络设备的应用特点,介绍了嵌入式linux的主要技术及在工业控制领域的应用方法。结合硬件平台详细说明了嵌入式linux系统的主要实现方法同时也简要介绍了该嵌入式系统的实时内核、内存机制和文件系统的设计等内容。 关键词:嵌入式系统;嵌入式linux;工业控制 1 前言 随着Internet的飞速发展,网络应用越来越广泛,对各种工业控制设备的网络功能要求也越来越高。当前的要求是希望工业控制设备能够支持TCP/IP以及其它Internet协议,从而能够通过用户熟悉的浏览器查看设备状态、设置设备参数,或者将设备采集到的数据通过网络传送到Windows或Unix/Linux服务器上的数据库中。这就要求工控系统必须具备两
[应用]
AVR串口多机通讯过程的解答
网友NE5532提问: AVR的串口说有多机通讯功能,就是在第一帧发送从机的地址,再发数据,呢么请问,从机的地址是在什么地方写的呢?是从机自己就包含地址(硬件)还是在软件上给从机赋值?DATASHEET里面好像没写啊。 -------------------------------------------------------------------------------- 马潮老师的解答: 根据你提的问题看,你对串口多机通信的过程是不清楚的,应该先深入了解和掌握多机通信的过程。 在多机通信过程中,所有设备的RS232接口是并在通信线上的,其中只能有一个设备为主机,其他为从机,通信由主机发起。数据帧一般采用1位起始位、9 位
[单片机]
arm linux 从入口到start_kernel 代码分析 - 5
4. 调用平台特定的 __cpu_flush 函数 当 __create_page_tables 返回之后 此时,一些特定寄存器的值如下所示: r4 = pgtbl (page table 的物理基地址) r8 = machine info (struct machine_desc的基地址) r9 = cpu id (通过cp15协处理器获得的cpu id) r10 = procinfo (struct proc_info_list的基地址) 在我们需要在开启mmu之前,做一些必须的工作:清除ICache, 清除 DCache, 清除 Writeb
[单片机]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved