本文概述:设备树驱动的probe函数不是被内核直接调用的,而是通过总线驱动框架间接调用。完整流程是:驱动注册到总线 → 总线遍历设备树节点 → compatible属性匹配成功 → 总线调用驱动的probe函数。以platform总线为例,调用链为:driver_register → bus_add_driver → driver_attach → __driver_attach → driver_probe_device → really_probe → 驱动的probe。如果probe没有被调用,优先检查compatible字符串是否完全匹配、驱动是否加载成功、设备节点是否正确创建。

核心要点速览

  • 调用方式:通过总线框架间接调用,不是内核直接调用

  • 匹配条件:设备树compatible属性与驱动of_match_table匹配

  • 调用时机:驱动加载且设备匹配成功时调用,顺序不固定

  • 返回值:成功返回0,失败返回负数错误码,返回错误则驱动绑定失败

  • 常见排查:compatible不匹配、驱动未加载、节点状态disabled、dmesg报错

面试题

Linux设备树驱动的probe函数是什么时候被调用的?调用流程是怎样的?如果probe没被调用,你怎么排查?

面试官考察点
这道题考察的是你对Linux驱动模型的理解深度。很多写过设备树驱动的人也说不清probe到底是谁调用的,只会说"系统调用的"。面试官想知道你是否理解总线-驱动-设备这个模型,以及遇到驱动不工作的时候有没有系统化的排查思路。

回答思路

probe函数调用完整流程

1
驱动注册阶段:insmod加载驱动模块时,调用module_platform_driver宏,最终会调用platform_driver_register,把驱动注册到platform总线上。
2
总线匹配阶段:驱动注册到总线后,总线会遍历自己管理的所有设备,对每个设备尝试和驱动进行匹配。匹配是双向的,既要检查驱动支持的设备,也要检查设备是否已有驱动。
3
compatible匹配:对于设备树驱动,匹配依据是compatible属性。总线会比较设备树节点的compatible字符串和驱动of_match_table中的字符串,如果有一项完全相同,就认为匹配成功。
4
调用probe:匹配成功后,总线会调用really_probe函数,在这个函数里完成一些准备工作(比如分配设备资源、设置电源状态),然后调用驱动中注册的probe函数指针。
5
probe执行:驱动的probe函数开始执行,完成设备的初始化工作:读取设备树属性、申请资源、注册字符设备、初始化硬件等。返回0表示成功,返回负数表示失败。

用代码来展示这个调用链的关键节点:

/* 驱动入口 */
module_platform_driver(my_driver);

/* 向下展开为 */
platform_driver_register(&my_driver);
  → driver_register(&drv->driver);
    → bus_add_driver(drv);
      → driver_attach(drv);
        → __driver_attach(dev, drv);
          → driver_match_device(drv, dev); // 匹配compatible
          → driver_probe_device(drv, dev);
            → really_probe(dev, drv);
              → dev->bus->probe(dev); // 总线probe
                → my_driver.probe(pdev); // 驱动probe

匹配的具体过程

设备树匹配的核心函数是of_driver_match_device。它会遍历驱动的of_match_table数组,把每一项的compatible字符串和设备节点的compatible属性做比较。注意compatible属性是可以有多个值的,设备树里写的是一个字符串列表,从最具体到最通用。内核会按顺序匹配,找到第一个匹配的就算成功。

/* 驱动侧 */
static const struct of_device_id my_of_match[] = {
  { .compatible = "vendor,my-chip-v2" },
  { .compatible = "vendor,my-chip" },
  { },
};

/* 设备树侧 */
my_device@1000 {
  compatible = "vendor,my-chip-v2", "vendor,my-chip";
  reg = <0x1000 0x100>;
};

面试官追问

追问1
如果probe没有被调用,你怎么排查?从哪些方面入手?
我一般按这个顺序排查:

第一步,看驱动有没有加载成功。用lsmod命令看模块在不在,或者dmesg看有没有模块加载的日志。如果模块都没加载成功,那probe肯定不会调用,先解决加载问题。

第二步,检查设备树节点。用ls /sys/bus/platform/devices/看设备有没有被正确创建。如果设备节点不存在,可能是设备树编译没包含进去,或者节点status是disabled状态。

第三步,核对compatible字符串。这是最常见的问题。设备树里写的和驱动of_match_table里的必须完全一致,大小写、连字符、厂商前缀都不能错。我就遇到过vendor前缀写错的问题,排查了半天才发现。

第四步,加调试打印。在probe函数最开头加一句pr_err打印,如果dmesg里没有这句,说明没进probe。还可以在probe之前的匹配函数里加打印,定位是哪一步出了问题。

第五步,检查其他可能因素。比如是不是驱动返回了EPROBE_DEFER(因为依赖的资源还没准备好),这种情况probe会被延后调用。还有是不是设备已经被其他驱动绑定了。
追问2
probe函数的返回值有什么含义?返回非0会怎么样?
probe函数返回0表示成功,驱动和设备绑定成功,后续可以正常工作。返回负数表示失败,常见的错误码有:

-ENODEV:设备不存在或不支持
-ENOMEM:内存分配失败
-EIO:硬件IO错误
-EPROBE_DEFER:依赖的资源还没准备好,需要延迟probe

如果probe返回错误,总线就会认为绑定失败,驱动不会和这个设备绑定。特别是EPROBE_DEFER这个返回值很重要,如果probe中申请的资源(比如时钟、gpio、regulator)还没就绪,就返回EPROBE_DEFER,内核会把这个设备放到延迟队列里,等其他驱动加载完了再重新尝试probe。
追问3
先有驱动还是先有设备?probe的调用时机是固定的吗?
这个问题挺好的,答案是都有可能。Linux的驱动模型是动态匹配的,不管是先有驱动还是先有设备,只要后来另一个出现了,总线就会尝试匹配。

具体来说:如果先加载驱动、后创建设备(比如设备树节点后面才注册),那么设备注册的时候会遍历总线上的驱动,找到匹配的就调用probe。反过来,如果设备已经存在了、驱动后加载,那么驱动注册的时候会遍历设备,找到匹配的就调用probe。

所以probe的调用时机不是固定的,取决于驱动和设备谁先出现在总线上。这也是为什么有些设备的probe调用顺序不固定,依赖加载顺序。对于有依赖关系的驱动,就要用EPROBE_DEFER机制来处理。
追问4
设备树里的status属性有什么用?和probe有什么关系?
设备树节点的status属性用来表示设备的状态,常见的值有:

"okay"或"ok":设备正常可用,会被正常创建和匹配
"disabled":设备被禁用,不会被创建,也就不会调用probe
"fail"或"fail-xxx":设备故障,不可用

最常用的是okay和disabled。很多时候同一款芯片用在不同产品上,有的产品用了某个外设有的没用到,就可以通过修改status来控制设备要不要启用。disabled的设备节点,内核不会为它创建platform_device,自然也就不会匹配驱动和调用probe了。

失分表达

常见失分回答

  • "系统启动的时候调用的" —— 太含糊,说明你对驱动模型不了解

  • "内核直接调用probe函数" —— 不对,是通过总线框架间接调用的

  • "没调用就是compatible写错了" —— 只说这一个原因,排查思路不全面

  • "probe返回0和返回1没区别吧" —— 有区别,非0就是失败,驱动绑定不上

准备动作

  • 对照内核源码,跟踪一遍platform_driver_register的调用路径

  • 亲手写一个最简单的platform驱动,加各种打印,看dmesg输出

  • 故意写错compatible,观察驱动加载后的现象,积累排查经验

  • 了解一下EPROBE_DEFER的机制和使用场景

  • 准备一个你实际调试过的驱动问题,讲清楚排查过程

核心关键词

Linux设备树、probe函数、platform驱动、驱动注册、compatible匹配、driver_register、嵌入式Linux面试