§1.2 drivers/base/platform.c:板级设备主战场
平台:QEMU virt + ARM64。platform 是伪总线:把 SoC 上不会自己报名的 MMIO 设备接到驱动模型。
0. 清单这条要明白什么
core.c 管设备怎么出现。
dd.c 管 match 成功之后怎么 probe / remove。
bus.c 管两张 klist。
本文件把这三份的槽填成 platform 业务:
- 设备怎么变成 platform :
parent/bus/ 名字 / claim 资源 /device_add。 - 驱动怎么挂上这条总线 :
__platform_driver_register把driver.bus指过来。 - 怎么匹配 :
platform_match------ virt 上是of_match_table↔compatible。 - 怎么进你的 probe :
platform_probe再转发platform_driver.probe(pdev)。
不是 把 1600 行读完。PM、DMA、irq affinity、platform_driver_probe()、bundle 跳过。
DT 节点怎么展开成 platform_device 在 drivers/of/platform.c(1.3)。
精读函数:
| 函数 | 角色 |
|---|---|
platform_bus_init |
启动早期登记:父设备 + 总线,两件东西 |
platform_device_add / register |
设备接到 platform 总线 |
__platform_driver_register |
驱动接到 platform 总线 |
platform_match |
bus->match:非 0 才 probe |
platform_match_id |
没 DT 时的旧名字表(第三档) |
platform_probe / platform_remove |
bus->probe / bus->remove,再转到驱动 |
platform_get_resource / get_irq / devm_platform_ioremap_resource |
probe 里取 reg / IRQ(扫一眼) |
可跳过:__platform_match、platform_find_device_by_driver(查找封装,probe 不走)。
1. 两个对象,不是一个
platform_bus_init()
→ device_register(&platform_bus) // 一台 struct device,默认父设备
→ bus_register(&platform_bus_type) // 那份 bus_type:两张 klist + 槽
| 对象 | 类型 | 出现在哪 | 干什么 |
|---|---|---|---|
platform_bus |
struct device |
/sys/devices/platform |
没指定 parent 时的挂靠点 |
platform_bus_type |
struct bus_type |
/sys/bus/platform |
.match / .probe / .remove,以及 klist_devices / klist_drivers |
platform_device_add 里若 !dev->parent,就 parent = &platform_bus。
之后 DT 展开、你的 platform_driver 才有地方挂。
platform_bus_type 槽位(前面 bus.c / dd.c 调的都是这些指针):
| 槽 | 填谁 | 谁调用 |
|---|---|---|
.match |
platform_match |
dd.c 的 driver_match_device |
.probe |
platform_probe |
dd.c 的 call_driver_probe(优先 bus->probe) |
.remove |
platform_remove |
dd.c 的 device_remove |
.uevent |
platform_uevent |
热插/模块别名:MODALIAS=of:N...Cdemo,hello |
2. 设备怎么挂上去
2.1 platform_device_register = initialize + add
对应 device_register:一次做完。中间多 setup_pdev_dma_masks(没设过 DMA mask 就默认 32-bit)。
调用约定:
- 你自己准备好
pdev(静态或kzalloc),填好name/id/resource,再调本函数。 - 不要和
platform_device_alloc叠用 :alloc 里已经device_initialize。alloc 出来的走platform_device_add。 - 返回 0 成功。进过 initialize 后失败不能
kfree,必须platform_device_put(里面是put_device)。 - 注销成对:
platform_device_unregister=platform_device_del+platform_device_put。
2.2 platform_device_add 五步
清单把「设备怎么变成 platform」落在这里看最清楚。返回 0 成功;失败会把已 insert 的 resource / 自动 ID 吐回去。
- parent :没指定就挂到
&platform_bus(/sys/devices/platform)。 dev->bus = &platform_bus_type:之后 match/probe 走 platform,不是 PCI/AMBA。- 名字 (sysfs 目录):
- 普通整数:
name.id PLATFORM_DEVID_NONE:只有namePLATFORM_DEVID_AUTO:IDA 拿号,写成name.id.auto;失败要把号还回去
- 普通整数:
- claim 资源 :
IORESOURCE_MEM进iomem_resource,IORESOURCE_IO进ioport_resource,避免两台设备抢同一段物理地址。 device_add:进core.c→ 入总线设备表、建 sysfs,再bus_probe_device。
2.3 virt DT 多数不进 platform_device_add
of_platform 自己设 dev->bus = &platform_bus_type,再 device_add(of_device_add)。
步骤等价:parent、bus、资源、进层次。改 virt.dts 加节点就是在枚举,不靠手写 platform_device_register。
3. 驱动怎么挂上去
platform_driver_register(drv)
= __platform_driver_register(drv, THIS_MODULE)
drv->driver.owner = owner
drv->driver.bus = &platform_bus_type // ★ 挂到这条总线
driver_register(&drv->driver) // bus.c:入 klist_drivers,再 attach
写驱动填的是 platform_driver.probe(struct platform_device *) ,不是 device_driver.probe。
driver.c 里那条 pr_warn:总线已经有 .probe 时不要再填 device_driver.probe(会被忽略)。
注销:platform_driver_unregister → driver_unregister。
4. 匹配:platform_match
bus_type.match。dd.c 的 driver_match_device 调这里,非 0 才 probe 。
英文 kerneldoc 还停留在旧的名字比对;今日 virt 真正走 OF 那一档。
四档顺序(有 driver_override 时只认指定驱动名,后面全跳过):
| 顺序 | 比什么 | virt 上 |
|---|---|---|
| 0 | pdev->driver_override == drv->name |
sysfs 强制绑定,清单可后放 |
| 1 | of_driver_match_device |
主路径 |
| 2 | acpi_driver_match_device |
用不到 |
| 3 | pdrv->id_table → platform_match_id |
没 DT 的旧名字表 |
| 4 | pdev->name == drv->name |
最后兜底 |
第 1 档展开:
of_driver_match_device(dev, drv)
= of_match_device(drv->of_match_table, dev) != NULL
→ of_match_node(matches, dev->of_node)
→ 扫 of_device_id[],对节点 compatible(1.3 再细看)
驱动侧表在 platform_driver.driver.of_match_table(类型 struct of_device_id,字段 compatible[128],见 mod_devicetable.h)。
设备侧字符串在 DT 节点的 compatible,展开后挂在 dev->of_node。
写对 compatible = "demo,hello":第 1 档命中,进 platform_probe。
写错:设备照样进 klist_devices 和 sysfs,卡在 platform_match,不 probe。
第 3 档细节(platform_match_id):
- 表是
struct platform_device_id[],以空name({ })收尾。 - 比的是
pdev->name,不是 sysfs 的name.id。 - 命中写入
pdev->id_entry(probe 可从里面读driver_data),返回该条;否则NULL。 - 有
id_table却一条都不中,不再用drv->name兜底。
5. probe / remove
5.1 platform_probe
call_driver_probe 优先调 bus->probe,所以会进这里,再转到 platform_driver.probe(pdev)。
进这里时 match 已经成功 。参数仍是 struct device *,先 to_platform_device。
返回值交给 dd.c:
| 返回 | 含义 |
|---|---|
| 0 | 绑定成功 |
| 非 0 | 拆掉半绑状态(devm_* 一并清) |
-EPROBE_DEFER |
进延迟队列稍后再试 |
probe 之前两件板级准备(virt 上多数空转):
of_clk_set_defaults:DTassigned-clocks,没有of_node就空转。dev_pm_domain_attach:挂 power domain。你的probe失败会detach,避免半挂。
platform_driver_probe() 是另一条旧路径(probe 放在 __init 里、不允许 defer)。命中时 .probe 已被换成 platform_probe_fail,直接 -ENXIO。清单跳过。
5.2 platform_remove
对称卸:优先 remove_new(void),否则旧的 remove(返回值会被忽略并警告),再 dev_pm_domain_detach。
6. probe 里怎么拿 reg / IRQ(扫一眼)
DT 的 reg / interrupts 展开后躺在 pdev->resource[](填表是 1.3)。本文件只提供取用:
| API | 干什么 |
|---|---|
platform_get_resource(pdev, type, n) |
按类型+下标取一条 struct resource,没有则 NULL |
platform_get_irq(pdev, n) |
取 IRQ 号;OF 上会走 of_irq_get,可能 -EPROBE_DEFER |
devm_platform_ioremap_resource(pdev, n) |
get MEM 资源 + devm_ioremap_resource;设备走时自动 unmap |
为何优先 devm_*:寿命挂在 struct device 上,remove / probe 失败时 devres_release_all 一起放。机制在 devres.c,本文件只是 platform 包装。
7. 查找封装
__platform_match:给 bus_find_device 用的适配器。回调原型是 (device, const void *),把 data 转回 device_driver * 再交给 platform_match。
不是 bus_type.match。probe 匹配走的是 platform_bus_type.match = platform_match。
platform_find_device_by_driver(start, drv):在设备表里找一台 match 能对上 的设备(不是看已经绑了谁)。
start = NULL从头扫;非 NULL 从下一个开始。- 命中多计一次引用,用完必须
put_device;没有则NULL。 - DRM 等用它找子设备再
device_link_add。probe 不走这里。