3. UART驱动核心数据结构:uart_driver、uart_port、uart_ops结构体详解
在Linux内核中,UART(通用异步收发传输器)驱动的实现依赖于三个核心数据结构:uart_driver、uart_port 和 uart_ops。它们分别承担驱动注册、端口管理和硬件操作抽象的角色。理解这三者的定义、字段含义及协作关系,是编写稳定、高效的UART驱动的基础。
核心概念: 这三个结构体构成了UART驱动的“三驾马车”。
uart_driver 负责向内核注册驱动实例;uart_port 描述具体的串口硬件端口;uart_ops 则是一组函数指针,定义了底层硬件的操作接口。三者通过指针相互引用,形成一个完整的驱动框架。
3.1 uart_driver:驱动注册的顶层容器
uart_driver 结构体是UART驱动向内核注册时的顶层描述。它包含了驱动名称、设备号、端口数量、以及指向 uart_port 数组的指针。驱动初始化时,必须填充该结构体并调用 uart_register_driver() 完成注册。
// 定义于 include/linux/serial_core.h
struct uart_driver {
struct module *owner; // 驱动所属模块
const char *driver_name; // 驱动名称(如 "ttyS")
const char *dev_name; // 设备名称(如 "ttyS")
int major; // 主设备号
int minor; // 起始次设备号
int nr; // 支持的端口数量
struct console *cons; // 关联的控制台(可选)
struct uart_state *state; // 端口状态数组(内核内部管理)
struct tty_driver *tty_driver; // 关联的TTY驱动
};
| 字段 | 类型 | 说明 |
|---|---|---|
owner |
struct module * |
通常设置为 THIS_MODULE,用于模块引用计数 |
driver_name |
const char * |
驱动内部名称,用于 /proc/tty/drivers 显示 |
dev_name |
const char * |
设备节点名称前缀,如 ttyS 生成 /dev/ttyS0 |
major |
int |
主设备号,可设为 0 由内核动态分配 |
nr |
int |
该驱动管理的最大端口数,决定 state 数组大小 |
注册流程示例:
static struct uart_driver my_uart_driver = {
.owner = THIS_MODULE,
.driver_name= "my_uart",
.dev_name = "ttyMY",
.major = 0, // 动态分配
.minor = 0,
.nr = 4, // 支持4个端口
};
static int __init my_uart_init(void) {
return uart_register_driver(&my_uart_driver);
}
3.2 uart_port:硬件端口的抽象描述
uart_port 是UART驱动中最核心的数据结构,它抽象了一个具体的串口硬件端口。该结构体包含了端口号、I/O基地址、中断号、时钟频率、FIFO大小、以及指向 uart_ops 的指针。每个物理串口对应一个 uart_port 实例。
// 定义于 include/linux/serial_core.h
struct uart_port {
spinlock_t lock; // 端口自旋锁
unsigned long iobase; // I/O端口基地址
unsigned char __iomem *membase; // 内存映射基地址
unsigned int irq; // 中断号
unsigned int uartclk; // UART时钟频率 (Hz)
unsigned int fifosize; // FIFO大小 (字节)
unsigned char x_char; // 待发送的XON/XOFF字符
unsigned int flags; // 端口标志 (UPF_*)
unsigned int line; // 端口索引号 (对应tty设备)
struct device *dev; // 关联的设备结构体
struct uart_ops *ops; // 硬件操作函数集
// ... 其他字段
};
| 字段 | 说明 |
|---|---|
lock |
保护端口寄存器和状态的自旋锁,中断上下文中使用 |
iobase / membase |
I/O端口或内存映射基地址,用于访问硬件寄存器 |
irq |
中断号,驱动通过 request_irq() 注册中断处理函数 |
uartclk |
UART模块的输入时钟频率,用于计算波特率 |
fifosize |
硬件FIFO深度,影响中断阈值和流控策略 |
line |
端口编号,与 ttySN 中的 N 对应 |
ops |
指向 uart_ops 的指针,驱动必须实现 |
注意事项:
uart_port 中的 lock 自旋锁必须在中断处理函数中使用 spin_lock_irqsave() 变体,以避免死锁。同时,ops 指针必须在端口初始化时正确赋值,否则内核在操作端口时会触发空指针异常。
3.3 uart_ops:硬件操作函数集
uart_ops 结构体定义了一组函数指针,作为UART驱动与硬件之间的操作契约。驱动开发者必须实现其中的核心回调函数,包括端口启动/停止、数据发送/接收、参数设置等。内核的串口核心层通过 uart_port->ops 调用这些函数,完成与硬件的交互。
// 定义于 include/linux/serial_core.h
struct uart_ops {
unsigned int (*tx_empty)(struct uart_port *);
void (*set_mctrl)(struct uart_port *, unsigned int mctrl);
unsigned int (*get_mctrl)(struct uart_port *);
void (*stop_tx)(struct uart_port *);
void (*start_tx)(struct uart_port *);
void (*stop_rx)(struct uart_port *);
void (*enable_ms)(struct uart_port *);
void (*break_ctl)(struct uart_port *, int ctl);
int (*startup)(struct uart_port *);
void (*shutdown)(struct uart_port *);
void (*flush_buffer)(struct uart_port *);
void (*set_termios)(struct uart_port *, struct ktermios *new,
struct ktermios *old);
void (*set_ldisc)(struct uart_port *, struct ktermios *);
void (*pm)(struct uart_port *, unsigned int state,
unsigned int oldstate);
int (*set_wake)(struct uart_port *, unsigned int state);
const char *(*type)(struct uart_port *);
void (*release_port)(struct uart_port *);
int (*request_port)(struct uart_port *);
void (*config_port)(struct uart_port *, int);
int (*verify_port)(struct uart_port *, struct serial_struct *);
int (*ioctl)(struct uart_port *, unsigned int, unsigned long);
};
| 回调函数 | 调用时机 | 典型实现 |
|---|---|---|
startup() |
端口首次打开时 | 申请中断、复位FIFO、使能接收中断 |
shutdown() |
端口关闭时 | 释放中断、关闭FIFO、禁用DMA |
start_tx() |
发送缓冲区有数据时 | 使能发送中断或触发DMA发送 |
stop_tx() |
发送缓冲区空或流控暂停时 | 禁用发送中断 |
set_termios() |
用户调用 tcsetattr() 时 |
设置波特率、数据位、校验位、停止位 |
tx_empty() |
查询发送器是否空闲 | 读取LSR寄存器,检查THR和TSR空标志 |
典型实现片段:
static int my_uart_startup(struct uart_port *port) {
// 申请中断
int ret = request_irq(port->irq, my_uart_irq_handler, 0,
"my_uart", port);
if (ret)
return ret;
// 复位FIFO,使能接收中断
writeb(UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR |
UART_FCR_CLEAR_XMIT, port->membase + UART_FCR);
writeb(UART_IER_RDI, port->membase + UART_IER);
return 0;
}
static const struct uart_ops my_uart_ops = {
.startup = my_uart_startup,
.shutdown = my_uart_shutdown,
.start_tx = my_uart_start_tx,
.stop_tx = my_uart_stop_tx,
.set_termios= my_uart_set_termios,
.tx_empty = my_uart_tx_empty,
// ... 其他回调
};
3.4 三者协作关系总结
在UART驱动中,uart_driver、uart_port 和 uart_ops 三者形成了清晰的层次化协作关系:
- uart_driver 是驱动实例的容器,负责向内核注册,并管理一组
uart_port。 - uart_port 是单个硬件端口的抽象,包含硬件资源信息(I/O地址、中断号、时钟等),并通过
ops指针绑定操作函数集。 - uart_ops 是硬件操作的回调集合,由驱动开发者实现,内核串口核心层在适当时机调用这些回调完成硬件交互。
| 结构体 | 角色 | 生命周期 | 关键关联 |
|---|---|---|---|
uart_driver |
驱动注册管理器 | 模块加载到卸载 | 包含 uart_port[] 数组 |
uart_port |
硬件端口抽象 | 设备探测到移除 | 持有 uart_ops * 指针 |
uart_ops |
硬件操作接口 | 与端口生命周期一致 | 被 uart_port 引用 |
最佳实践: 在编写UART驱动时,建议将
uart_ops 定义为 static const,以节省内存并确保回调函数集在运行时不被修改。uart_port 通常嵌入在驱动自定义的端口结构体中,通过 container_of() 宏访问私有数据。