4. UART驱动初始化流程:模块入口函数、platform_driver注册、设备树匹配与probe流程

UART 驱动的初始化是驱动生命周期中的第一步,也是设备与驱动绑定的关键阶段。在 Linux 内核中,UART 驱动通常以 platform_driver 的形式存在,通过设备树(Device Tree)进行硬件描述与匹配。本节将详细拆解从模块入口到 probe 函数被调用的完整路径。

核心要点: UART 驱动的初始化遵循“模块入口 → 注册 platform_driver → 设备树匹配 → probe 执行”的固定流程。理解这一链条是编写和调试 UART 驱动的基础。

4.1 模块入口与出口函数

每个 Linux 驱动模块都包含一对标准的入口和出口函数。对于 UART 驱动,入口函数通常完成 platform_driver 的注册,出口函数则负责注销。

/* 模块入口函数 */
static int __init uart_driver_init(void)
{
    int ret;

    ret = platform_driver_register(&uart_platform_driver);
    if (ret)
        pr_err("Failed to register UART platform driver: %d\n", ret);
    else
        pr_info("UART platform driver registered successfully\n");

    return ret;
}

/* 模块出口函数 */
static void __exit uart_driver_exit(void)
{
    platform_driver_unregister(&uart_platform_driver);
    pr_info("UART platform driver unregistered\n");
}

module_init(uart_driver_init);
module_exit(uart_driver_exit);
函数/宏 作用 调用时机
module_init() 指定模块入口函数 insmod 或内核启动时
module_exit() 指定模块出口函数 rmmod 时
platform_driver_register() 向内核注册 platform 驱动 入口函数中调用
platform_driver_unregister() 从内核注销 platform 驱动 出口函数中调用
最佳实践: 使用 __init 和 __exit 标记可以优化内存占用。入口函数在初始化完成后会被内核自动释放,出口函数在非模块编译时也会被丢弃。

4.2 platform_driver 结构体定义与注册

platform_driver 是驱动与内核框架之间的桥梁。它包含了 probe、remove、shutdown 等回调函数,以及用于设备树匹配的 of_match_table。

/* 设备树匹配表 */
static const struct of_device_id uart_of_match[] = {
    { .compatible = "vendor,uart-1.0", },
    { .compatible = "vendor,uart-2.0", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, uart_of_match);

/* platform_driver 结构体定义 */
static struct platform_driver uart_platform_driver = {
    .probe  = uart_probe,
    .remove = uart_remove,
    .shutdown = uart_shutdown,
    .driver = {
        .name = "vendor_uart",
        .of_match_table = uart_of_match,
        .pm = &uart_pm_ops,  /* 电源管理操作 */
    },
};
注意: MODULE_DEVICE_TABLE(of, ...) 宏必须与 of_match_table 配合使用。它会在模块编译时生成设备表,供内核在设备树解析时进行匹配。缺少此宏将导致驱动无法通过设备树自动加载。

4.3 设备树匹配机制

设备树(Device Tree)是描述硬件资源的扁平数据结构。UART 设备在设备树中的节点通常包含 compatible、reg、interrupts 等属性。内核通过 compatible 字符串与驱动的 of_match_table 进行匹配。

/* 设备树中的 UART 节点示例 */
uart0: serial@10000000 {
    compatible = "vendor,uart-1.0";
    reg = <0x10000000 0x1000>;
    interrupts = <0 33 4>;
    clocks = <&clk_uart0>;
    clock-names = "uart_clk";
    status = "okay";
};

匹配流程如下:

  1. 内核启动时解析设备树,为每个 compatible 属性生成一个 platform_device。
  2. 驱动调用 platform_driver_register() 后,内核遍历已注册的 platform_device。
  3. 将设备的 compatible 值与驱动的 of_match_table 逐项比较。
  4. 匹配成功时,内核调用驱动的 probe 函数,并将匹配到的 of_device_id 指针传入。
匹配优先级: 设备树匹配优先于传统的 platform_device.name 匹配。如果 of_match_table 不为空,内核会优先使用设备树匹配路径。

4.4 probe 函数执行流程

probe 函数是驱动初始化的核心。它负责从设备树中提取硬件资源、初始化 UART 端口、注册串口设备等。以下是一个典型的 UART probe 函数结构:

static int uart_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct uart_port *port;
    struct resource *res;
    int ret, irq;

    /* 1. 分配 uart_port 结构体 */
    port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
    if (!port)
        return -ENOMEM;

    /* 2. 获取 I/O 内存资源 */
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    port->membase = devm_ioremap_resource(dev, res);
    if (IS_ERR(port->membase))
        return PTR_ERR(port->membase);

    /* 3. 获取中断号 */
    irq = platform_get_irq(pdev, 0);
    if (irq < 0)
        return irq;
    port->irq = irq;

    /* 4. 获取时钟资源 */
    port->uartclk = uart_get_clk_rate(dev);

    /* 5. 初始化 UART 硬件 */
    uart_hw_init(port);

    /* 6. 注册串口驱动 */
    ret = uart_add_one_port(&uart_driver, port);
    if (ret) {
        dev_err(dev, "Failed to add UART port\n");
        return ret;
    }

    platform_set_drvdata(pdev, port);
    dev_info(dev, "UART port initialized at 0x%llx, irq=%d\n",
             (unsigned long long)res->start, irq);

    return 0;
}
资源管理: 使用 devm_ 系列函数(如 devm_kzalloc、devm_ioremap_resource)可以自动管理资源释放,避免 probe 失败或 remove 时的内存泄漏。

4.5 完整初始化流程 SVG 架构图

下图展示了从模块入口到 probe 完成的完整数据流与调用关系:

UART驱动初始化流程架构图 UART驱动初始化流程架构图 module_init() platform_driver_register() platform_driver .probe = uart_probe .remove = uart_remove .driver.of_match_table 设备树节点 compatible = "vendor,uart-1.0" reg / interrupts 匹配成功 → 内核调用 probe 函数 uart_probe() 内部执行步骤 ① 分配 uart_port ② 获取 MEM 资源 ③ 获取中断号 ④ 获取时钟 ⑤ 初始化 UART 硬件 ⑥ uart_add_one_port() ⑦ platform_set_drvdata() probe 返回 0 → UART 设备就绪,等待中断与数据收发

4.6 初始化流程关键点总结

阶段 关键函数/结构 常见错误 调试方法
模块入口 module_init() 入口函数未正确导出 检查 dmesg 中驱动加载日志
驱动注册 platform_driver_register() 返回值非0,注册失败 打印返回值,检查驱动名称是否冲突
设备树匹配 of_match_table compatible 字符串不匹配 使用 dtc 反编译设备树,核对字符串
probe 执行 uart_add_one_port() 资源获取失败或中断号无效 检查 /proc/iomem 和 /proc/interrupts
调试技巧: 在 probe 函数中添加 dev_dbg() 或 dev_info() 打印关键资源值(如物理地址、中断号、时钟频率),可以快速定位初始化失败的原因。同时,使用 CONFIG_OF 和 CONFIG_SERIAL_CORE 内核配置选项确保设备树和串口核心支持已开启。