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";
};
匹配流程如下:
- 内核启动时解析设备树,为每个
compatible属性生成一个 platform_device。 - 驱动调用
platform_driver_register()后,内核遍历已注册的 platform_device。 - 将设备的
compatible值与驱动的of_match_table逐项比较。 - 匹配成功时,内核调用驱动的 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 完成的完整数据流与调用关系:
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 内核配置选项确保设备树和串口核心支持已开启。