ARM64 Linux 6.10 内核驱动(12):dd.c:probe/ remove、延迟 probe

§1.2 drivers/base/dd.cprobe / remove、延迟 probe(-EPROBE_DEFER

平台:QEMU virt + ARM64。文件名 dd = device-driver:设备和驱动怎么绑、怎么拆。


0. 清单这条要明白什么

1. 和 core.c 怎么接上

core.c 不直接调 dd.c。中间隔着 bus.c

复制代码
core.c   device_add()
           └─ bus_probe_device(dev)          // bus.c
                 └─ device_initial_probe()   // dd.c 入口
                       └─ __device_attach(dev, true)

device_initial_probe 几乎是空壳:__device_attach(dev, true)

true = 允许异步 probe(启动注册)。旁边的 device_attach()false,给用户态手工 bind,必须同步做完。

gdb 断 PL011 / 自己的 platform_driver.probe,栈上应能看到:

复制代码
device_add
  → bus_probe_device
      → device_initial_probe / __device_attach
          → __device_attach_driver
              → driver_probe_device
                  → really_probe
                      → call_driver_probe
                          → bus->probe   // platform 或 amba

写错 compatible 时:device_add 仍然成功 (sysfs 有设备),卡在 match,进不了 really_probe。清单练习「故意写错不 probe」考的就是这一段。


2. 匹配发生在 probe 之前

__device_attach 对「还没有 dev->driver」的设备(启动常见)做:

复制代码
bus_for_each_drv(dev->bus, ..., __device_attach_driver)

回调 __device_attach_driver每一个 driver:

  1. driver_match_device(drv, dev)
    drv->bus->match ? match(dev, drv) : 1
    virt 上 platform 设备就是 platform_match(先 of_driver_match_device)。
  2. 返回 0 :不匹配,换下一个(写错 compatible)。
  3. 返回 -EPROBE_DEFER :match 自己说现在不能定,进延迟队列,停扫,别绑到别的 driver。
  4. 返回 >0 :匹配成功 → driver_probe_device

bus_for_each_drv:回调返回 0 继续;非 0 停扫。

driver_probe_device 里 probe 失败会把错误取反成正数再返回 0 给回调,好让下一个 driver 再试-EPROBE_DEFER(负或取反后的正)则停扫并入队。

另一方向:驱动后注册(insmod)走 driver_attachbus_for_each_dev__driver_attach,逻辑对称(每个 device 做 match + probe)。

清单概念「三段如何匹配」:本文件只完成「在总线上遍历 + 调用 bus->match」。字符串怎么比,见下一份 platform.c


3. 同步 / 异步 probe(启动优化,不是 defer)

不是「match 异步」,而是 probe 能不能拖到工作队列里跑,避免一个慢驱动堵住启动。

谁算异步驱动:driver_allows_async_probing()

PROBE_PREFER_ASYNCHRONOUS,或 cmdline driver_async_probe=,或模块声明异步。

默认 platform_driver(包括清单要写的 demo)是同步

同一套回调扫两趟 (不能在 bus_for_each_drv 里直接踢线程,drv 指针扫到下一个可能失效):

第一趟 __device_attach 第二趟 async helper
want_async false:只要同步驱动 true:只要异步驱动
调度 当前线程当场 probe async_schedule_devasync_wq

过滤:check_async && async_allowed != want_async 则返回 0 当没看见。

第一趟没绑上且扫到过异步驱动(have_async),解锁后再排第二趟。

device_attach()(sysfs bind)check_async=false:不分两趟。


4. probe 怎么调起来

4.1 driver_probe_device:外壳 + defer 收口

match 已成功。真正干活的是 __driver_probe_device(检查 dead/已绑定、拉父设备和供应商 runtime PM、进 really_probe)。

这个函数自己多出来的是清单要的 defer:

  • probe_count / probe_waitqueuewait_for_device_probe() 等启动扫完。
  • 若返回 -EPROBE_DEFER 正数 EPROBE_DEFERdriver_deferred_probe_add;probe 期间 deferred_trigger_count 变过则再 trigger 一次,避免刚入队被漏踢。

为什么两个值都认:really_probecall_driver_probe 失败会 ret = -ret,好和 -ENODEV(设备状态错)区分。

4.2 really_probe:绑定现场

顺序:

  1. defer_all_probes 或供应商 device_link 没好 → 直接 -EPROBE_DEFER(virt 上很少)。
  2. dev->driver = drv
  3. pinctrl / dma_configure(virt 基本空转;真 SoC 没 pinctrl 可能 defer)。
  4. driver_sysfs_add/sys/bus/<bus>/drivers/<drv>/ 下的设备链接)。
  5. call_driver_probe ← 清单「probe 怎么调起来」。
  6. 成功:driver_bound(进 driver 设备链表,并从 defer 队列摘掉自己,再 trigger 等着的消费者)。
  7. 失败:错误取反;device_remove + device_unbind_cleanup

CONFIG_DEBUG_TEST_DRIVER_REMOVE 会故意 remove 再 probe 一次,默认关。

4.3 call_driver_probe:进具体驱动

复制代码
if (dev->bus->probe)
    bus->probe(dev);          // 优先
else if (drv->probe)
    drv->probe(dev);

virt 上两条常见总线:

设备 dev->bus->probe 再到
清单假节点 platform_driver platform_probeplatform.c platform_driver.probe(pdev)
PL011 UART amba_probeamba/bus.c amba_driver.probe(pcdev, id)

本函数只按返回值分类打印,不改返回值:

返回 含义
0 绑上
-EPROBE_DEFER 依赖没好,请再试
-ENODEV / -ENXIO 硬件不认,可换下一个 driver
其它负值 probe 自己出错(ioremap 失败等)

5. 延迟 probe(-EPROBE_DEFER

典型原因(真 SoC):probeclk_get / regulator_get 时供应商还没 probe。virt 上时钟几乎总开着,很少 defer,但第 1 个月过关要能讲清机制。

5.1 两张表

复制代码
probe 返回 -EPROBE_DEFER
    → driver_deferred_probe_add()     只进 pending
         pending  ── trigger ──►  active
                                    │
                                    ▼
                       deferred_probe_work_func
                         → 再 bus_probe_device()   // core.c 那条链重走
  • pending :等着被踢。driver_deferred_probe_add 只往这里挂。
  • activetrigger 把 pending splice 过来,work 串行重试。

can_match == false 不入队:根本不可能有驱动(写错 compatible 是 match 失败,不是 defer)。已在队列则跳过,避免重复挂。

5.2 谁 trigger

某台设备 driver_bound 成功(「我这个 clk 好了」)、late_initcall 打开 defer 机制、超时 delayed work。

driver_deferred_probe_trigger():pending → active,然后:

c 复制代码
queue_work(system_unbound_wq, &deferred_probe_work);

5.3 线程怎么分工

没有 dd.c 自己 kthread_create 的专用线程。

角色 谁在跑 干什么
第一次 probe init 线程 / insmod 进程 / 异步 probe 的 kworker 成功则 trigger;defer 则挂 pending
重试 system_unbound_wqevents_unbound)上一条 work 串行 bus_probe_device
异步 probe 另一张 async_wq __device_attach_async_helper,与 defer 无关

deferred_probe_mutex 只保护链表 ,work 在 bus_probe_device 前会解锁,所以重试 A 时别人仍可把 B 挂进 pending。竞态用 deferred_trigger_count 补踢。

和异步 probe 对照:

延迟 probe 异步 probe
解决什么 依赖还没好 慢驱动别堵住启动
排队 system_unbound_wq async_schedule_devasync_wq
跑什么 deferred_probe_work_func __device_attach_async_helper

late_initcall(deferred_probe_initcall)driver_deferred_probe_enable = true 并 flush 一轮,避免启动高峰期 defer 队列搅局。


6. remove:和 probe 对称

正规卸载:rmmoddriver_detach 扫该驱动绑过的设备;或 sysfs unbind → device_release_driver

复制代码
device_release_driver(dev)
  → device_release_driver_internal(dev, NULL, NULL)   // 加锁;drv=NULL = 谁绑着拆谁
      → __device_release_driver
            → device_remove              // 通知驱动
            → device_unbind_cleanup      // 内核收尾

device_release_driver_internal 自己不进模块 ,只加锁并核对 !drv || drv == dev->driverrmmod 带具体 drv,避免拆错人)。

6.1 device_removecall_driver_probe

复制代码
优先 bus->remove(platform 是 platform_remove → platform_driver.remove)
否则 drv->remove

probe 失败时 really_probe 也会走到,避免半绑状态。

不要在自己的 .remove 里对同一台 设备再调 device_release_driver,会在 device_lock 上死锁。

6.2 device_unbind_cleanup ↔ 绑定状态

不调驱动。devres_release_alldevm_* 一起放掉(清单 devm_* 的机制在 devres.c,这里是调用点),然后 dev->driver = NULL、清 drvdata。

顺序必须是:先 device_remove(驱动指针还在),再 cleanup。


7. virt 对照

复制代码
DT 节点 / 假节点 demo,hello
  → of_platform_populate → platform_device
  → device_add(core.c)
  → bus_probe_device
  → platform_match(下一份笔记)
  → platform_probe → 你的 probe 里 pr_info

PL011 走 AMBA,gdb 断的是 amba_probe,不是 platform_probe。清单练习写最小驱动请走 platform。

initcall_debug__driver_probe_device 改走 really_probe_debug,打印 probe 耗时,里面仍是 really_probe


相关推荐
ThornArmor1 小时前
重铸1996|肉体渲染:关节的裂缝:分段模型积木拼装与未成熟的 RSP 矩阵骨骼动画形变
c语言·汇编·c++·python·硬件架构·游戏机
Doraemomo3 小时前
IMX6ULL裸机开发——系统主频和时钟配置
arm开发·嵌入式硬件·makefile·imx6ull
白色的北极熊3 小时前
c语言 scanf 输入流有空格,逗号 说明
c语言
Logic1013 小时前
C语言/数据结构位运算题解:异或XOR找出货船中的“独特载货量“——只出现一次的数字
c语言·数据结构·数组·位运算·时间复杂度·算法题·异或性质
yunwei375 小时前
eBPF 示例教程:实现 `scx_nest` 调度器
linux·后端·性能优化
朽棘不雕5 小时前
关于重定向的小补充
linux
T1mzhou5 小时前
ARM64 平台安全学习记录
linux·服务器·arm开发·arm
GG-_-Bond6 小时前
基于dpdk实现udp接收数据
linux·网络·udp
吴声子夜歌6 小时前
Shell编程实例——bash入门
linux·运维·shell