§1.2 drivers/base/dd.c:probe / 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:
driver_match_device(drv, dev)
→drv->bus->match ? match(dev, drv) : 1
virt 上 platform 设备就是platform_match(先of_driver_match_device)。- 返回 0 :不匹配,换下一个(写错
compatible)。 - 返回
-EPROBE_DEFER:match 自己说现在不能定,进延迟队列,停扫,别绑到别的 driver。 - 返回 >0 :匹配成功 →
driver_probe_device。
bus_for_each_drv:回调返回 0 继续;非 0 停扫。
driver_probe_device 里 probe 失败会把错误取反成正数再返回 0 给回调,好让下一个 driver 再试 ;-EPROBE_DEFER(负或取反后的正)则停扫并入队。
另一方向:驱动后注册(insmod)走 driver_attach → bus_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_dev → async_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_waitqueue:wait_for_device_probe()等启动扫完。- 若返回
-EPROBE_DEFER或 正数EPROBE_DEFER:driver_deferred_probe_add;probe 期间deferred_trigger_count变过则再trigger一次,避免刚入队被漏踢。
为什么两个值都认:really_probe 里 call_driver_probe 失败会 ret = -ret,好和 -ENODEV(设备状态错)区分。
4.2 really_probe:绑定现场
顺序:
defer_all_probes或供应商device_link没好 → 直接-EPROBE_DEFER(virt 上很少)。dev->driver = drv。- pinctrl /
dma_configure(virt 基本空转;真 SoC 没 pinctrl 可能 defer)。 driver_sysfs_add(/sys/bus/<bus>/drivers/<drv>/下的设备链接)。call_driver_probe← 清单「probe 怎么调起来」。- 成功:
driver_bound(进 driver 设备链表,并从 defer 队列摘掉自己,再trigger等着的消费者)。 - 失败:错误取反;
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_probe(platform.c) |
platform_driver.probe(pdev) |
| PL011 UART | amba_probe(amba/bus.c) |
amba_driver.probe(pcdev, id) |
本函数只按返回值分类打印,不改返回值:
| 返回 | 含义 |
|---|---|
| 0 | 绑上 |
-EPROBE_DEFER |
依赖没好,请再试 |
-ENODEV / -ENXIO |
硬件不认,可换下一个 driver |
| 其它负值 | probe 自己出错(ioremap 失败等) |
5. 延迟 probe(-EPROBE_DEFER)
典型原因(真 SoC):probe 里 clk_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只往这里挂。 - active :
trigger把 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_wq(events_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_dev → async_wq |
| 跑什么 | deferred_probe_work_func |
__device_attach_async_helper |
late_initcall(deferred_probe_initcall) 才 driver_deferred_probe_enable = true 并 flush 一轮,避免启动高峰期 defer 队列搅局。
6. remove:和 probe 对称
正规卸载:rmmod → driver_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->driver(rmmod 带具体 drv,避免拆错人)。
6.1 device_remove ↔ call_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_all 把 devm_* 一起放掉(清单 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。