04、UART子系统详解
一、串口子系统框架
串口子系统框架是 Linux 内核中 包含了多个层级, 每个层级负责处理不同的功能和任务, 从而实现串口设备的完整驱动和管理。 接下来依次介绍每个层级的作用。
- 应用层: 位于最顶层, 是串口子系统中用户空间应用程序与内核空间之间的接口, 应用层包括了用户空间的串口应用程序, 如串口通信工具 minicom 等。
- 字符设备层: 位于应用层的下方, 负责将用户空间的串口读写请求传递给内核空间的tty_core 层。 字符设备层将串口设备看作一个特殊的字符设备, 通过字符设备接口来进行操作。
- tty_core 层: 位于字符设备层的下方, 是 Linux 内核中用于管理串口设备的核心模块, 它处理串口设备的基本功能, 如数据传输, 控制, 缓冲管理等。 tty_core 层与具体的串口硬件无关, 是串口设备的通用处理层。
- uart_core 层: 位于 tty_core 层的下方, 提供了串口设备的底层驱动接口, 负责与具体的串口硬件进行通信。 uart_core 层负责控制串口数据的发送和接收、 中断处理、 时钟管理等底层操作。
- 硬件层: 位于最底层, 是串口子系统中与具体硬件有关的部分。 硬件层包括了串口硬件的驱动程序, 与具体的串口控制器进行通信, 实现对硬件的底层控制和操作。
串口子系统框架的层级设计将串口设备的功能分解为不同的层次, 实现了功能的模块化和抽象化, 方便开发者进行串口设备的驱动开发和维护。
二、uart 相关底层结构体
芯片厂家需要实现的内容:
- uart_drvier 结构(一个)
- uart_port 结构(多个)
- uart_ops 对串口的操作集(可能一个,可能多个)
以下是更直白简洁的版本:
- uart_driver
这是个串口控制器的"总管",里面包含了 tty_driver 这个核心组件。它的主要任务是把多个部件(比如所有串口的资源信息)整合在一起,让底层硬件驱动不需要直接操心复杂的 tty 配置。 - uart_state
这是描述一个具体串口的"资源仓库"。
- 包含一个环形缓冲区(就像数据暂存池,用来存待发送的数据)
- 有 port 成员连接到 tty 系统
- 通过 uart_port 指针指向具体的硬件端口信息
- uart_port
这是管理物理串口的"操作台":
- 存储硬件寄存器的地址(比如芯片上的通信端口位置)
- 提供串口操作接口:读写数据(serial_in/serial_out)、配置参数(set_termios)、处理中断(handle_irq)
- 通过设备树(DTS)配置生成,最终被平台总线注册到系统中
- uart_ops
这是芯片厂商提供的"硬件操作工具包":
- 包含具体芯片的读写寄存器方法、中断处理等底层操作
- 驱动开发者可以直接调用这些接口,不需要关心硬件细节
- 比如:某款芯片的发送数据函数、设置波特率函数都会在这里实现
::: 关系图:
uart_driver(总管) → 包含多个 uart_state(资源仓库)
uart_state(资源仓库) → 通过 uart_port(操作台)连接到硬件
uart_port(操作台) → 通过 uart_ops(工具包)调用芯片底层功能
这样整个结构就像餐厅的流水线:
总管(uart_driver)协调所有服务员(uart_state)
每个服务员(uart_state)拿着操作台(uart_port)
操作台(uart_port)通过工具包(uart_ops)直接操作厨房设备(硬件) :::
1.1、uart_driver(驱动 方法)
uart_driver表示一个串口控制器驱动的抽象,内部包含了tty_driver类型的成员变量,同时也包含了该串口控制器所支持的所有串口对应uart_state
uart_driver 定义在 include/linux/serial_core.h
文件中
struct uart_driver 封装了 tty_driver, 使得底层的 UART 驱动不需要关心 tty_driver。
填充案例:
1.2、uart_port (device 硬件相关信息)
uart_port 是针对一个串口的抽象, 定义在 kernel/include/linux/serial_core.h
, 其内部包含一个 tty_port 类型的成员变量, 内容如下:
填充位置:serial8250_isa_init_ports
1.3、uart_state
struct uart_state 是一个结构体, 定义在 kernel/include/linux/serial_core.h, 通常用于表示UART 驱动程序的状态信息。 通过 uart_driver 结构体中的 state 成员指针, 可以访问和操作与UART 设备状态有关的数据。 struct uart_state 内容如下:
/*
* 结构体定义 UART 驱动程序的状态。
*/
struct uart_state {
struct tty_port port; // tty 端口结构体
enum uart_pm_state pm_state; // UART 电源管理状态
struct circ_buf xmit; // 待发送数据的循环缓冲区
atomic_t refcount; // 引用计数
wait_queue_head_t remove_wait; // 等待队列头用于移除操作
struct uart_port *uart_port; // UART 端口结构体指针
};
2
3
4
5
6
7
8
9
10
11
1.4、uart_ops
uart_port
uart_ops定义了针对UART的一系列操作,包括发送、接收及线路设置等,如果说tty_driver中的tty_operations对于串口还较于抽象,那么uart_ops则直接面向了串口硬件操作。
实例化如下:
三、底层uart_driver 注册进tty核心层
我们打开 Linux 内核源码, kernel/drivers/tty/serial/8250/8250_core.c 文件中的函数主要是与 8250 系列 UART 驱动程序的核心功能相关的。 这个文件实现了 8250 这种串行通信设备的核心操作, 包括初始化、 配置、 中断处理、 数据传输等功能。
打开 kernel/drivers/tty/serial/8250/8250_core.c 文件, 如下所示
- uart_register_driver (一次调用)
- uart_add_one_port (多次调用)
/driver/tty/serial/serial_core.c
文件提供了uart
核心逻辑,其中最重要的是uart
控制器的注册和uart
设备的注册。其中控制器的注册注册接口是:
该函数主要完成以下工作:
- 调用
alloc_tty_driver
申请tty_driver
类型的内存,并设置tty_driver
的各成员变量,同时设置其ops
成员为uart_ops
。 - 根据串口的个数,为每一个串口申请对应的
uart_state
类型的内存空间(也就完成了对应tty_port
的创建),用于存储每一个串口的资源相关的信息以及串口对应tty_port
信息,并为tty_port
设置其ops
成员为uart_port_ops
;我们知道tty_port
中包含接收数据的缓存,但却没有发送数据的缓存,而uart_state
中则包含了发送数据的缓存(即环形缓存区),因此借助uart_state
、tty_port
则实现了串口收发数据的缓存(在我们之前实现的虚拟串口中也定义了数据发送的buff
,只不过是使用kfifo
实现的)。
下面看下uart
设备的注册函数:
该函数主要完成以下工作:
- 完成
uart_state
与uart_port
的互相关联。 - 调用
uart_configure_port
,配置该串口(如mem
、io
资源的申请等)。 - 调用
tty_port_register_device_attr
接口,完成tty_port
与tty_driver
的关联,并调用device_register
完成tty_port
对应device
的注册,同时该动作也完成了tty_port
对应device
与tty_class
的关联,同时也完成tty
对应字符设备的创建(通过向应用层发送kobject add uevent
,而udevd
、mdev
在接收到该uevent
后,根据设备节点至,通过调用mknod
接口完成设备文件inode
的创建,也就完成了字符设备文件的创建),同时也为该tty
端口对应的device
创建了uart
相关的属性信息(即tty_dev_attr_groups
)。
2.1、串口控制器驱动注册uart_register_driver
注册接口为uart_register_driver
,而针对uart_driver
的注册,开发人员主要需要设置uart_driver
的driver_name
、dev_name
、nr
、cons
,其中dev_name
、nr
表示串口的名称前缀、串口个数,而cons
则表示该串口控制器是否支持控制台功能,若支持控制台则设置该变量。
目录:kernel\drivers\tty\serial\serial_core.c
实现:
/**
* uart_register_driver - register a driver with the uart core layer
* @drv: low level driver structure
*
* Register a uart driver with the core driver. We in turn register
* with the tty layer, and initialise the core driver per-port state.
*
* We have a proc file in /proc/tty/driver which is named after the
* normal driver.
*
* drv->port should be NULL, and the per-port structures should be
* registered using uart_add_one_port after this call has succeeded.
*/
int uart_register_driver(struct uart_driver *drv)
{
struct tty_driver *normal;
int i, retval;
BUG_ON(drv->state);
/*
* Maybe we should be using a slab cache for this, especially if
* we have a large number of ports to handle.
*/
//根据串口的个数,为每一个串口申请对应的uart_state类型的内存空间
drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
if (!drv->state)
goto out;
//申请tty_driver型的内存
normal = alloc_tty_driver(drv->nr);
if (!normal)
goto out_kfree;
drv->tty_driver = normal;
//设置tty_driver的各成员变量
normal->driver_name = drv->driver_name;
normal->name = drv->dev_name;
normal->major = drv->major;
normal->minor_start = drv->minor;
normal->type = TTY_DRIVER_TYPE_SERIAL;
normal->subtype = SERIAL_TYPE_NORMAL;
normal->init_termios = tty_std_termios;
normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
normal->driver_state = drv;
//设置其ops成员为uart_ops
tty_set_operations(normal, &uart_ops);
/*
* Initialise the UART state(s).
*/
for (i = 0; i < drv->nr; i++) {
struct uart_state *state = drv->state + i;
struct tty_port *port = &state->port;
tty_port_init(port);
port->ops = &uart_port_ops;
port->close_delay = HZ / 2; /* .5 seconds */
port->closing_wait = 30 * HZ;/* 30 seconds */
}
retval = tty_register_driver(normal);
if (retval >= 0)
return retval;
for (i = 0; i < drv->nr; i++)
tty_port_destroy(&drv->state[i].port);
put_tty_driver(normal);
out_kfree:
kfree(drv->state);
out:
return -ENOMEM;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
案例:
2.2、串口的注册接口uart_add_one_port
/**
* uart_add_one_port - attach a driver-defined port structure
* @drv: pointer to the uart low level driver structure for this port
* @uport: uart port structure to use for this port.
*
* This allows the driver to register its own uart_port structure
* with the core driver. The main purpose is to allow the low
* level uart drivers to expand uart_port, rather than having yet
* more levels of structures.
*/
int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
{
struct uart_state *state;
struct tty_port *port;
int ret = 0;
struct device *tty_dev;
int num_groups;
BUG_ON(in_interrupt());
if (uport->line >= drv->nr)
return -EINVAL;
state = drv->state + uport->line;
port = &state->port;
mutex_lock(&port_mutex);
mutex_lock(&port->mutex);
if (state->uart_port) {
ret = -EINVAL;
goto out;
}
//1、完成uart_state与uart_port的互相关联
state->uart_port = uport;
state->pm_state = UART_PM_STATE_UNDEFINED;
uport->cons = drv->cons;
uport->state = state;
/*
* If this port is a console, then the spinlock is already
* initialised.
*/
if (!(uart_console(uport) && (uport->cons->flags & CON_ENABLED))) {
spin_lock_init(&uport->lock);
lockdep_set_class(&uport->lock, &port_lock_key);
}
if (uport->cons && uport->dev)
of_console_check(uport->dev->of_node, uport->cons->name, uport->line);
//2、调用uart_configure_port,配置该串口(如mem、io资源的申请等)
uart_configure_port(drv, state, uport);
num_groups = 2;
if (uport->attr_group)
num_groups++;
uport->tty_groups = kcalloc(num_groups, sizeof(*uport->tty_groups),
GFP_KERNEL);
if (!uport->tty_groups) {
ret = -ENOMEM;
goto out;
}
uport->tty_groups[0] = &tty_dev_attr_group;
if (uport->attr_group)
uport->tty_groups[1] = uport->attr_group;
//3、完成tty_port与tty_driver的关联等操作
/*
* Register the port whether it's detected or not. This allows
* setserial to be used to alter this port's parameters.
*/
tty_dev = tty_port_register_device_attr(port, drv->tty_driver,
uport->line, uport->dev, port, uport->tty_groups);
if (likely(!IS_ERR(tty_dev))) {
device_set_wakeup_capable(tty_dev, 1);
} else {
dev_err(uport->dev, "Cannot register tty device on line %d\n",
uport->line);
}
/*
* Ensure UPF_DEAD is not set.
*/
uport->flags &= ~UPF_DEAD;
out:
mutex_unlock(&port->mutex);
mutex_unlock(&port_mutex);
return ret;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
案例:
serial8250_register_ports
serial8250_register_8250_port