Linux驱动开发-14、串行设备驱动模型

时间:2022-03-17 23:36:02

串行设备驱动模型

1、TTY概念解析

a) 串口终端(/dev/ttyS*

串口终端是使用计算机串口连接的终端设备;Linux以字符设备来处理这种串行端口;这些端口所对应的设备名称是/dev/ttySAC0-N

 

b) 控制台终端(/dev/console

计算机的输出设备通常被称为控制台终端,特指printk信息输出到的设备;/dev/console是一个虚拟设备;它需要映射到真正的tty上,这可以在内核启动参数中配置

 

c) 虚拟终端(/dev/tty*

用户登录时,使用的是虚拟终端 ,tty0是当前使用虚拟终端的别名

 

2、TTY子系统架构

a) tty 核心:为tty驱动提供接口,隔离上层应用与底层硬件

b) tty线路规程:加工与tty驱动交互的数据(数据的格式化等),勾勒串行层的行为,有助于复用底层的代码来支持不同的技术

i. N_TTY -----> /dev/ttySX    (终端)

ii. N_IRDA----> /dev/ircommX (红外)

iii. N_PPP -----> ppp0

 

c) tty驱动:关注uart或者其他底层串行硬件特征的驱动程序

d) 串行子系统有提供一些内核API

e) 架构图

 

Linux驱动开发-14、串行设备驱动模型

Linux驱动开发-14、串行设备驱动模型 

 

3、回溯函数:dump_stack(); 使用方法:将该函数加入到要回溯的函数中去,之后内核启动会自动串口打印回溯信息

 

4、串口驱动重要数据结构:

struct uart_driver:一个结构表示一个驱动;驱动能支持多个串口

struct uart_port:一个结构表示一个实在的串口:如串口0,串口1

struct uart_ops:串口操作集合,TTY最终调用的读写功能都在里面定义关联函数

 

 

5、串口驱动为了将自身和内核联系起来,必须完成两个步骤:

a) 通过调用:uart_register_driver(struct uart_driver *);向串行核心注册

b) 通过调用:uart_add_one_port(struct uart_driver*,struct uart_port *),注册其支持的每个端口

 

传统的UART使用TTY驱动程序为

/driver/serial/serial_core.c


USB-串行端口转换器的TTY驱动程序在目录:

 driver/usb/serial/usb-serial.c

TTY架构驱动追踪分析

1、分析思路

a) TTY属于字符设备

b) TTY为分层架构

 

2、初始化设备

a) 串口在CPU启动时引导向内核注册为平台设备

b) 设备驱动初始化在prob函数进行

c) 初始化需要完成以下内容:

i. 获取端口(一个端口就为一个串口)

ii. 初始化端口

iii. 添加端口

iv. 向内核申请私有空间

v. 创建属性设备文件

vi. 提供调频支持

 

 Linux驱动开发-14、串行设备驱动模型

 

 

 

3、打开设备:上层调用到底层的历程:

a) 思路:用户打开open函数;系统需要寻找对应的file_operations

b) file_operations 是由谁注册的?驱动中找找

 

底层驱动-----------------------------------------------------------------------------------------------------------

c) 查看驱动只有uart_register_driver();

 

TTY核心---------------------------------tty_io.c/h----------------------------------------------------------------

 

d) 进入查看,串口驱动向tty核心注册了tty_drivertty_register_driver(normal)

e) 查看tty_register_driver():在这里注册了字符设备

i. cdev_init(&driver->cdev, &tty_fops);

ii. cdev_add(&driver->cdev, dev, driver->num);

f) 注册字符设备对应的操作函数集在

i. &tty_fops  --> static const struct file_operations tty_fops

ii.  ---->tty_fops.open

iii.  ----> tty_open(struct inode *inode, struct file *filp)

iv. 在tty_open函数中对struct tty_operations uart_ops进行操作,通过下面语句

v. ----->retval = tty->ops->open(tty, filp); 通过这层调用进入TTY驱动层

 

TTY驱动----------------------------------serial_core.c/h------------------------------------------------------

 

vi. ----> 跟踪 struct tty_operations uart_ops结构体中的open函数

vii. ----> 进入static int uart_open(struct tty_struct *tty, struct file *filp)

viii. ----> 该函数又调用 uart_startup()函数

ix. ----> 该函数通过retval = uport->ops->startup(uport);进入驱动层

 

底层驱动--------------------------------------------------------------------------------------------------------

 

x. ---->最终调用到驱动程序中的struct uart_ops中的操作集

xi. ----> int (*startup)(struct uart_port *);

4、总结:用户应用 的 open 通过层层调用到达驱动函数中的 xxx_startup(...);

xxx_startup函数中需要完成的任务

1、使能串口接收功能:rx_enabled(port) = 1;

2、注册数据接收中断处理程序:request_irq();

3、使能发送功能:tx_enabled(port) = 1;

4、注册发送中断处理函数:request_irq();

5、出错处理

 

 

 

6、写设备

a) 思路:用户打开write函数;系统需要寻找对应的file_operations

b) file_operations 是由谁注册的?驱动中找找

 

TTY核心---------------------------------tty_io.c/h----------------------------------------------------------------

 

c) 进入查看,串口驱动向tty核心注册了tty_drivertty_register_driver(normal)

d) 查看tty_register_driver():在这里注册了字符设备

i. cdev_init(&driver->cdev, &tty_fops);

ii. cdev_add(&driver->cdev, dev, driver->num);

e) 注册字符设备对应的操作函数集在

i. &tty_fops  --> static const struct file_operations tty_fops

ii.  ---->tty_fops.write函数

iii.  ----> tty_write(struct inode *inode, struct file *filp)

iv. 在该函数中调用了:do_tty_write(ld->ops->write, tty, file, buf, count)

 

TTY线路规程------------------------------tty_ldisc.c/h  n_tty.c--------------------------------------

 

v. 以上传递进来的是ld->ops  ==  struct tty_ldisc_ops *ops;

vi. ops->write 调用到struct tty_ldisc_ops tty_ldisc_N_TTY.write函数

vii. 在n_tty_write函数中对struct tty_operations uart_ops进行操作,通过下面语句

viii. -----> tty->ops->write(tty, b, nr);通过这层调用进入TTY驱动层

 

TTY驱动----------------------------------serial_core.c/h------------------------------------------------------

 

ix. ----> 跟踪 struct tty_operations uart_ops结构体中的write函数

x. ----> 进入uart_write

xi. ----> 该函数又调用了uart_ops结构中的uart_startup()函数

xii. ---->接着再调用void __uart_start(struct tty_struct *tty)

xiii. ----> 之后再进过指针传递调用驱动程序

 

底层驱动--------------------------------------------------------------------------------------------------------

 

xiv. ---->最终调用到驱动程序中的struct uart_ops中的操作集

xv. ---->  void (*start_tx)(struct uart_port *);

在发送函数中需要做的就是

1、使能发送中断

2、具体发送在中断函数中进行

a) 判断x_char 是否为0,不为0则发送x_char (x_char用来通知数据缓存是否忙碌)

b) 判断发送换从是否为空:uart_circ_empty();或者是驱动设置为停止发送状态:uart_tx_stoped(),则取消发送

c) 循环发送直到循环缓冲为空

i. 发送FIFO满,退出发送

ii. 将要发送的数据写入发送寄存器

iii. 修改循环缓冲位置

d) 如果发送缓冲有空闲空间,则唤醒发送进程: uart_wake_up();

e) 如果发送缓冲为空,则关闭发送使能:uart_tx_stoped();

 

 

7、设备读

a) 同理从用户层到驱动,最终调用到驱动的

i. 其中要注意的是线路规程中的n_tty_read函数,不直接操作底层驱动的串口缓存,需要通过tty->read_buf[tty->read_tail];其中的数据是串口驱动通过发送函数(tty_push将数据推送到该buf中)

b) 驱动中的发送函数处理流程

蓝色底为内核提供的函数

while(max_count-- > 0) /*这个用来平衡系统的性能*/

{

i. 读取UPFCON寄存器rd_regl();

ii. 读取UFSTAT寄存器  rd_regl();

iii. 通过以上读取的数据判断FIFO缓存是否为空,空则退出循环

iv. 读取UERSTAT寄存器

v. 读取URXH,从该寄存器中取出字符

vi. 流控处理

vii. 根据UFSTAT记录错误类型

viii. 如果收到的是sysrq字符,进行特出处理,调用内核函数:

1. uart_handle_sysrq_char(port, ch)

ix. 把接收到的字符送到串口驱动的uart_insert_char();

}

最后一步:将串口缓存中的数据推送到tty->read_buftty_flip_buffer_push();

 

 

 

流控:Linux使用的是自动硬件流控 或者是 软件流控-->使用x_char来通讯标识