十一、UART&TTY驱动
Linux系统中UART驱动和TTY驱动两者有着紧密的关系,它们不像I2C和SPI驱动是单独一个模块,分析时应当将它们看成一个整体来分析。UART驱动部分依赖于硬件平台,而TTY驱动和具体的平台无关。本文的分析内容基于IMX6DL硬件平台和Kernel 3.0.35版本,虽然UART部分依赖于平台,但是不管是哪个硬件平台,驱动的思路都是一致的,下面分模块来分别介绍。
一、UART驱动
UART驱动主要涉及的驱动文件是imx.c、serial_core.c两个文件。首先我们找到驱动的入口函数module_init(imx_serial_init),在函数imx_serial_init中调用uart_register_driver向内核注册了一个驱动,在该函数中除了做常规的初始化驱动之外,有两个关键点的函数调用需要我们注意一下,如下图:

先是调用tty_set_operations将uart_ops这一个tty设备的操作函数集设置到了tty驱动中,同时调用tty_register_driver函数向内核注册了tty驱动,其中uart_ops的数据类型及内容如下:

当调用tty_open函数时就会调用这里的uart_open,具体是怎么调用的,我们后面会分析到。imx_serial_init函数中还调用platform_driver_register向内核注册了一个平台设备,所以UART驱动即是平台设备又是字符设备。当驱动和设备匹配时会调用serial_imx_probe函数,在该函数中除了做具体平台相关的串口端口设置,比如调用platform_get_resource获取中断资源,赋值sport->timer.functioni = mx_timeout设置定时器之外,还有一个关键的操作就是sport->port.ops = &imx_pops,赋值了跟具体硬件平台的底层操作函数,当中的imx_pops结构体如下:

该结构体中的函数都是和具体的硬件平台相关,串口的数据接收、注册中断接收函数、使用DMA接收数据等操作都是在上面的函数中完成,这些函数由NXP官方提供,是和底层硬件最接近的函数。
跟其他的驱动一样,当打开串口设备时,uart_open函数得到调用,在tty_open函数中调用了uart_startup函数来启动串口,如下:

在uart_startup函数中通过uport->ops->startup(uport);间接调用到了imx_startup函数,因为我们在前面已经通过sport->port.ops = &imx_pops将相关硬件平台的串口操作函数赋值给了抽象的串口端口操作函数,所以到这里我们转去分析imx_startup看看里面做了什么操作。
在imx_startup中通过调用request_irq(sport->rxirq, imx_rxint, 0, DRIVER_NAME, sport)注册了串口中断接收函数imx_rxint,串口中断发送函数同理,同时如果板级文件中设置启用了DMA,还初始化了用于DMA数据处理相关的工作队列,如下图:

我们并未配置使用DMA,所以只分析中断接收函数imx_rxint。Imx_rxint函数如下:


imx_rxint函数在循环中读取数据寄存器的值,并在函数的末尾调用了两个很关键的函数,分别是tty_insert_flip_char(tty, rx, flg)和tty_flip_buffer_push(tty),其中tty_insert_flip_char函数的作用是将接收到的字符放入tty数据块中,如下图:

而tty_flip_buffer_push(tty)则是将tty数据块的数据推到线路规程当中,线路规程相关的知识我们后面会讲到,这个函数的作用就类似于通知tty去线路规程获取从串口过来的数据,函数内容如下:

其中有个关键的操作就是调用了工作队列,具体这个工作队列是在何时被注册或者初始化,我们后面讲tty时候会分析到。总结以上,如果中断函数中只调用tty_insert_flip_char函数的话,tty是没办法获取串口数据的,还必须使用tty_flip_buffer_push函数将数据推到线路规程当中去。至此,UART到TTY这条路径我们就分析完了,接下来分析TTY的框架。
一、TTY驱动
TTY驱动不依赖具体的硬件平台,主要涉及的文件是tty_io.c、tty_ldisc.c。TTY驱动框架中包含一个叫线路规程的核心模块,TTY驱动不能直接从UART获取数据,所有的数据都必须从ldisc(线路规程获取)。首先我们来看tty相关的初始化,在前面注册UART驱动的时候,同时调用了tty_register_driver(normal)函数向内核注册了一个tty驱动,在该函数中调用了cdev_init(&driver->cdev, &tty_fops),向设备绑定了tty设备的操作函数集,tty_fops的数据类型是struct file_operations,该变量如下图:

因此当应用层打开一个tty设备时候会调用这个函数集当中的tty_open函数,接下来我们看tty_open函数里面做了什么操作。在tty_open函数中调用tty_init_dev(driver, index, 0)函数对tty设备进行了初始化,在tty_init_dev函数中又调用了initialize_tty_struct(tty, driver, idx)函数对tty相关的结构体进行了初始化,如下图所示:

其中有三个地方需要我们重点关注,第一个是tty_ldisc_init(tty),调用该函数完成了线路规程的初始化,在tty_ldisc_init函数里面通过调用tty_ldisc_get获得线路规程,在tty_ldisc_get函数中通过调用get_ldops(disc)获得线路规程的操作函数,如图所示:



其中tty_ldiscs是一个全局数组,数组元素类型是struct tty_ldisc_ops,也就是线路规程的操作函数集,类型如下图:

线路规程的操作函数具体是在什么时候被赋值初始化的,我们后面会分析到。
在initialize_tty_struct函数中第二个需要我们关注的函数调用是tty_buffer_init(tty),,
调用该函数完成了tty数据块相关的初始化,如下图所示:

在初始化函数中还初始化了一个工作队列,INIT_WORK(&tty->buf.work, flush_to_ldisc)。
具体这个工作队列是在何时被调用呢?就是在我们前面分析imx_rxint中断接收函数时,调用了tty_flip_buffer_push,在该函数中通过schedule_work(&tty->buf.work)调度了该工作队列。至此,TTY也和UART联系上了。
在initialize_tty_struct函数中需要我们关注的地方是tty->ops = driver->ops语句。前面我们分析到,在串口注册时候调用tty_set_operations函数,通过driver->ops = op将tty的操作函数赋值给了uart驱动,在这里则是将注册进去的函数给拿出来赋值给了tty设备,等于是应用层操作tty设备就是操作uart串口。在tty_init_dev函数中,除了初始化tty设备之外,还调用tty_ldisc_setup(tty, tty->link)函数对线路规程进行了设置。在tty_ldisc_setup函数中调用了tty_ldisc_open函数,该函数中使用ld->ops->open(tty)打开了线路规程,但是线路规程的操作函数是在哪里进行赋值的呢?保留这个疑问,我们接下来分析线路规程相关的初始化流程。
记得前面我们提到的一个全局数组tty_ldiscs吗?这个数组的元素类型就是线路规程的操作函数。我们在内核代码中进行全局搜索,发现在tty_register_ldisc函数中进行了设置,如下图:

调用该函数的话,就会将线路规程设置到全局数组tty_ldiscs中,那么tty_register_ldisc函数是在哪里被调用的呢?答案是,在tty_ldisc_begin函数中被调用,如下图:

而tty_ldisc_N_TTY变量就是线路规程的操作函数,变量赋值如下图:

tty_ldisc_begin这个函数被console_init调用,那是谁又调用了console_init呢?答案是在/init/main.c文件中,asmlinkage void __init start_kernel(void)函数调用了console_init。而start_kernel函数正是内核的入口函数。也就是说,在进入内核的时候,第一时间就先初始化了tty的线路规程,赋值了线路规程的相关操作函数。那线路规程的操作函数又是在哪里被调用的呢?
前面我们讲过,tty驱动不能直接从串口获得数据,数据的来源是线路规程,那么调用线路规程的读写函数只能是tty的操作函数,所以我们来看看之前从未分析的tty_read和tty_write函数。首先来看tty_read函数,如下图:

果不其然,在tty_read中通过ld->ops->read调用了线路规程的read函数,也就是调用了tty_ldisc_N_TTY的ntty_read函数。我们再来看tty_write函数,如下图:

同样是调用到了线路规程的n_tty_write函数。
综上,在进入内核的时候,先是设置了线路规程的操作函数,然后在tty驱动注册的时候设置了tty的操作函数,并在后续打开tty设备时调用tty_open函数,在open函数中通过get_ldops(disc)获得线路规程的操作函数。当应用层调用tty_read读取数据时就调用了n_tty_read获得了数据。
十一、UART&TTY驱动的更多相关文章
- 一、uart&tty驱动
一.I.MX6 UART驱动 文件路径:\linux_IMX6_CoreC_3..35_for_Linux\drivers\tty\serial\imx.c .驱动入口函数:imx_serial_in ...
- [tty与uart]3.tty驱动分析
转自:http://www.wowotech.net/linux_kenrel/183.html 目录: 1 首先分析设备驱动的注册 1.1 uart_register_driver分析 1.2 tt ...
- [uart]3.tty驱动分析
转自:http://www.wowotech.net/linux_kenrel/183.html 目录: 1 首先分析设备驱动的注册 1.1 uart_register_driver分析 1.2 tt ...
- linux UART串口驱动开发文档
转:http://www.360doc.com/content/10/0417/18/829197_23519037.shtml linux UART串口驱动开发文档时间:2010-01-09 14: ...
- Linux TTY驱动--Serial Core层【转】
转自:http://blog.csdn.net/sharecode/article/details/9197567 版权声明:本文为博主原创文章,未经博主允许不得转载. 接上一节: Linux TTY ...
- tiny4412 串口驱动分析六 --- TTY驱动架构
转载: http://www.linuxidc.com/Linux/2013-11/92639.htm 参考: http://blog.csdn.net/lamdoc/article/details/ ...
- Linux tty驱动架构
Linux tty子系统包含:tty核心,tty线路规程和tty驱动.tty核心是对整个tty设备的抽象,对用户提供统一的接口,tty线路规程是对传输数据的格式化,tty驱动则是面向tty设备的硬件驱 ...
- STC8H开发(十一): GPIO单线驱动多个DS18B20数字温度计
目录 STC8H开发(一): 在Keil5中配置和使用FwLib_STC8封装库(图文详解) STC8H开发(二): 在Linux VSCode中配置和使用FwLib_STC8封装库(图文详解) ST ...
- Linux TTY驱动--Uart_driver底层【转】
转自:http://blog.csdn.net/sharecode/article/details/9196591 版权声明:本文为博主原创文章,未经博主允许不得转载. Linux 中将串口驱动进行了 ...
随机推荐
- 详解Java中的IO输入输出流!
目录 本片要点 基本分类 发展史 文件字符流 输出的基本结构 流中的异常处理 异常处理新方式 读取的基本结构 运用输入与输出完成复制效果 文件字节流 缓冲流 字符缓冲流 装饰设计模式 转换流(适配器) ...
- css 13-CSS3属性:Flex布局图文详解
13-CSS3属性:Flex布局图文详解 #前言 CSS3中的 flex 属性,在布局方面做了非常大的改进,使得我们对多个元素之间的布局排列变得十分灵活,适应性非常强.其强大的伸缩性和自适应性,在网页 ...
- 抢先看:笔者亲历的2020年中国.NET开发者大会活动纪实
2020年中国.NET开发者大会活动纪实 1 2020年12月19日的苏州工业园区,天公作美,阳光明媚,气象迷人,正是一个搞事的好日子.在这里,数百名中国.NET开发者们汇聚一堂,怀揣着激情和梦想,一 ...
- 抖音爬虫教程,python爬虫采集反爬策略
一.爬虫与反爬简介 爬虫就是我们利用某种程序代替人工批量读取.获取网站上的资料信息.而反爬则是跟爬虫的对立面,是竭尽全力阻止非人为的采集网站信息,二者相生相克,水火不容,到目前为止大部分的网站都还是可 ...
- Kubernetes应用管理器OpenKruise之CloneSet
OpenKruise OpenKruise 是 Kubernetes 的一个标准扩展,它可以配合原生 Kubernetes 使用,并为管理应用容器.sidecar.镜像分发等方面提供更加强大和高效的能 ...
- U8CO使用C#版(一)
1.懒加载: object obj = null; System.Type oType = System.Type.GetTypeFromProgID("U8Login.clsLogin&q ...
- 【vue】入门介绍
一.前端开发工具vscode 前端代码编写工具,使用vscode:vscode官网 安装好之后,可以先装如下几个插件,方便后续的开发. 二.编写代码 1.vscode快捷键生成html代码 在vsco ...
- SQL语句实现增删改查
查询语句SELECT *FROM mydriect WHERE id=1; 删除语句DELETE FROM mydriect WHERE id=1; 修改语句UPDATE mydriect SET 自 ...
- canvas--总结二
canvas图形绘制 矩形绘制 rect(x,y,w,h) 没有独立路径 strokeRect(x,y,w,h) 有独立路径,不影响别的绘制 fillRect(x,y,w,h) 有独立路径,不影响别 ...
- flume伪分布式安装
flume伪分布式安装: 1.导包:apache-flume-1.7.0-bin.tar.gz 2.配置环境变量:/etc/profile export FLUME_HOME=/yang/apache ...