串口驱动模型

一.tty概念:

1.1串口终端:通常是一种字符设备:用tty来简称。对应的文件一般为dev/ttyS*表示.dev/ttySAC0表示串口0.

1.2 控制台终端/dev/console:是一种虚拟设备。printk就是输出到控制台,但是要输出到实际设备需要将控制台和具体设备关联起来。例如将控制台关联到串口/dev/console=dev/ttySAC0,将printk输出从串口输出。

1.3 虚拟终端:用户登录用的就是虚拟终端tty1-tty6,使用ctcl+alt+f1~f6切换,tty0是当前使用终端的一个别名。

1.4 三种终端的关系:串口终端是实际的硬件设备,控制台终端是内核printk输出的地方,虚拟终端是给用户应用程序使用(通过应用登陆然后进行操作的入口),控制台和虚拟终端都要通过串口终端这个实际的硬件设备来进行实际的输入输出操作。

二.tty架构:tty核心,tty线路规程,tty驱动

2.0 架构调用流程:

用户空间: write() read()

tty核心: tty_write() tty_read()

线路规程:ldisc.write() ldisc.read()

ldisc.receive_buf()

tty驱动:driver.write tty_flip_buffer_push()

中断处理函数

硬件:串口硬件

2.1 tty核心:对用户提供统一的接口

2.2 tty线路规程:对传输数据进行格式化

2.3 tty驱动:面向tty设备的硬件驱动

三.初始化串口:

3.1 基础概念:重要的文件*/driver/*/serial/samsung.c是210/2440/6410通用的串口驱动文件。此目录下还有与处理器型号相关的的文件,文件名包含处理器型号例如xx210.c,xx2440.c。(不同处理器采用的指令集不一样(arm/x86:架构不一样,哈佛/冯诺依曼架构:哈佛将指令和数据存储分开而另外一个不分开,哈弗精简高效但功能相对少),还有指令集相同但是芯片厂对芯片的硬件配置不一样,一般嵌入式采用arm指令集的哈弗架构,精简高效)。

3.2 串口初始化:

重要结构:和混杂设备驱动结构不一样

UART驱动程序结构:struct uart_driver 驱动结果描述符,记录驱动的相关属性。

UART端口结构:struct uart_port 是个数组,长度由管理端口数量决定,其中重要包含的成员uart_ops。

UART相关操作函数集:struct uart_ops,集成操作这个端口的内核函数,应用层的操作函数映射到此集合成员中。

UART结构状态:struct uart_state

UART信息结构:struct uart_info

初始化流程:

(1)驱动注册函数:在samsung.c中找到调用注册函数uart_register_driver,它注册了驱动结构,驱动结构的具体初始化赋值按照具体情况进行修改就好。

(2)平台总线驱动注册:在文件(**处理器型号.c)中,调用平台驱动注册函数platform_driver_register再次注册对应处理器型号的串口驱动描述结构体。在平台总线结构体中probe函数对驱动进行了具体硬件的初始化。

(3)驱动描述符中匹配函数probe函数:**_serial_probe();重要流程

①初始化端口:取出匹配到串口的端口信息,调用函数**_serial_init_port();来初始化port。首先:重要的是platform_get_resource获取物理地址,然后转换成虚拟地址。(此处采用静态映射:内核就将串口的虚拟地址S3C_VA_UART准备好了,此时只要将端口物理地址去掉基地址来获取端口的物理地址实际偏移量再加上分配的好的虚拟地址就可以了)。其次:调用函数platform_get_irq_platdev();来获取设备终端号。最后:重设fifos(缓冲区清空),重启uart。

②添加端口:uart_add_one_port

③创建属性文件:device_create_file

④初始化动态频率调节:**_serial_cpufreq_register();

四.打开串口:

函数调用流程:open ---> tty_open(tty_ops里面的) ---> uart_open(结构体tty_operations里面的) ---> uart_start内核tty中定义的一个普通函数  --->串口端口的ops中驱动函数.startup回调;

XX_serial_startup所作的工作:

1.使能串口接受功能rx_enabled(port)=1;

2.为数据接收注册中断处理函数request_irq(接受中断号,接受处理函数,...);

3.使能串口发送功能tx_enabled(port)=1;

4.为数据发送注册中断处理函数request_irq(发送中断号,发送处理函数,...);

五.发送功能:

函数调用流程:write---> tty_write ---> n_tty_write(线路规程里面) ---> uart_write串口ops中函数---> uart_start串口驱动中的一个函数,用来循环缓冲 ---> 端口ops中.start_tx回调

XX_serial_start_tx所作的工作:使能接受中断号,再次使能接受发送功能。

循环缓冲:

处于调用过程中:在uart_start过程中会将应用层的数据存储在循环缓冲的数据结构中,当发送中断在函数ops中.start_tx中被激活时,就会去循环缓冲中读取数据写入到寄存器的写入对应的物理地址中去。

中断处理函数所作的工作:

  1. 判断x_char是否为0,如果不为0,则发送x_char
  2. 如果发送缓冲为空或者驱动被设置为停止发送的状态,则取消发送。
  3. 循环发送,循环条件:发送不为空

3.1如果发送存储器为满,退出发送

3.2将要发送的字符写入寄存器

3.3修改循环缓冲的的尾部位置

  1. 如果 发送缓冲中的剩余数据是uart_circ_chars_pengding<256,则唤醒之前阻塞的发送进程uart_write_wakeup
  2. 如果发送的缓冲为空,则关闭发送使能。

六.接受数据:

函数调用流程:read->tty_read->n_tty_read(struct tty_ldisc_ops tty_ldisc_N_TTY线路规程中)这个函数此时就截至了。不会再往后面深层次的调用,那么他是然后和驱动设备中函数产生关系的呢?这个函数会读一个read_buf,这个buf的值就是来源驱动的中断处理程序,当中断产生时,中断处理函数中从硬件寄存器读取的值最终会写入到这个buf中(中间还有一层缓冲区)。

流控:在进行数据传输时,会有硬件缓冲区用来临时存储数据,但是缓冲区是有限的,因此需要标志位来进行流量控制。

硬件设置:串口设备除开rx,tx口以外,还有sts,rts口用来控制。通过寄存器的剩余空间来设置Rts的电平,rts接通信设备的sts(sts的电平状态会被动和对方rts电平状态一样),设备能否发送数据就通过sts口来判断。数据发送端的sts口电平为高说明对方将自己的自己的rts设置为高电平,说明对方的存储空间满了,不能进行数据发送。

设置方式:自动设置和非自动设置。非自动设置就是用代码去判断fifo寄存器的中标志位来手动设置寄存器sts的值。自动设置是硬件层面上去将io口拉高拉低。

接受中断函数所作的工作:

  1. 读取UFCON寄存器
  2. 读取UFSTAT寄存器(用来判断FIFO中的数据数据量的)
  3. 判断FIFO的数据量,如果为0则退出。(共享中断会导致这情况)
  4. 读取UERSTAT寄存器
  5. 从URXH中取出接收到的字符,一个字节一个字节的读。
  6. 进行流控处理
  7. 通过判断UERSTAT寄存器的值记录具体错误类型
  8. 如果收到sysRq字符进行特殊处理uart_handle_sysrq_char
  9. 把接受到的字符一个一个送到串口驱动的buf,最多能接受多少个字符可以自己修改。
  10. 把驱动的buf送到线路规程的read_buf。tty_flip_buffer_push

猜你喜欢

转载自blog.csdn.net/qq_43706825/article/details/103717575