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驱动
};
uart_driver 关键字段说明
字段 类型 说明
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);
}
uart_driver 结构体与注册流程 uart_driver owner: THIS_MODULE driver_name: "my_uart" dev_name: "ttyMY" major: 0 (动态) nr: 4 state: uart_state[4] 调用 uart_register_driver() 向 TTY 层注册驱动 注册 TTY 核心 tty_driver 设备节点 state[i] 包含 uart_port 数组 (nr=4)

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;            // 硬件操作函数集
    // ... 其他字段
};
uart_port 关键字段说明
字段 说明
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 指针必须在端口初始化时正确赋值,否则内核在操作端口时会触发空指针异常。
uart_port 结构体与硬件映射关系 uart_port line: 0 irq: 38 iobase: 0x3F8 uartclk: 1843200 fifosize: 16 ops: &my_uart_ops lock: spinlock_t dev: &pdev->dev 映射到 硬件寄存器 RBR / THR / IER IIR / FCR / LCR 调用 uart_ops .startup() .shutdown() 触发 中断控制器 IRQ 38 CPU 中断

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);
};
uart_ops 核心回调函数说明
回调函数 调用时机 典型实现
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,
    // ... 其他回调
};
uart_ops 回调函数调用流程 用户空间 TTY 核心层 uart_ops 调度 startup() 申请中断,初始化硬件 start_tx() 使能发送中断 set_termios() 设置波特率/数据格式 UART 硬件寄存器 UART 硬件寄存器 UART 硬件寄存器 说明:uart_ops 是驱动与硬件之间的桥梁,每个回调函数对应一个特定的硬件操作场景。 内核串口核心层通过 uart_port->ops 指针调用这些函数,实现硬件无关的驱动框架。

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() 宏访问私有数据。