arch_initcall 何时被调用及 device_node 何时加入 platform 总线
问题一:arch_initcall 何时被调用?
结论
arch_initcall 是在 start_kernel() → rest_init() → kernel_init() → kernel_init_freeable() → do_basic_setup() → do_initcalls() 的链路中被调用的。
此时:
CPU 已初始化
内存管理已就绪
调度器已启动
设备树已经解析成 device_node 树
用户空间还未启动
调用链路
start_kernel()
│
│ ← Linux 内核入口
│
├── lockdep_init()
├── setup_arch(&command_line)
│ ↑
│ │ ← DTB 在这里被 setup_machine_fdt() 解析
│ │ ← unflatten_device_tree() 在这里执行
│ │ ← of_root 指向设备树根节点
│ │
├── sched_init()
├── page_alloc_init()
│ ...
│
└── rest_init()
│
├── kernel_thread(kernel_init, ...) // 创建 1 号进程(init 进程)
└── kernel_thread(kthreadd, ...) // 创建 kthreadd 进程
│
▼
kernel_init_freeable()
│
├── smp_prepare_cpus()
├── do_pre_smp_initcalls() // 0~3 级 early initcall
├── smp_init()
├── sched_init_smp()
│
└── do_basic_setup() ← arch_initcall 在这里
│
├── cpuset_init_smp()
├── usermodehelper_init()
├── shmem_init()
├── driver_init() // 驱动子系统初始化
├── init_irq_proc()
├── do_ctors()
├── usermodehelper_enable()
│
└── do_initcalls() ← arch_initcall 在这里
│
├── do_initcall_level(0) // early
├── do_initcall_level(1) // core
├── do_initcall_level(2) // postcore
├── do_initcall_level(3) // arch ← customize_machine()
├── do_initcall_level(4) // subsys
├── do_initcall_level(5) // fs
├── do_initcall_level(6) // device
└── do_initcall_level(7) // late
arch_initcall 的定义
在 ARM 架构下(i.MX6ULL 是 ARM),定义在:
#define arch_initcall(fn) __define_initcall(fn, 3)
数字 3 表示它是第 3 级 initcall,对应 arch 这一级。
所有 initcall 级别从低到高:
| 级别 | 名称 | 数字 | 典型用途 |
|---|---|---|---|
| 0 | early | 0 | 最早期初始化,如 lockdep、kmem_cache_init_pre_early |
| 1 | core | 1 | 核心子系统初始化 |
| 2 | postcore | 2 | post-core 初始化 |
| 3 | arch | 3 | 架构相关初始化,如 customize_machine |
| 4 | subsys | 4 | 子系统初始化 |
| 5 | fs | 5 | 文件系统初始化 |
| 6 | device | 6 | 设备驱动初始化 |
| 7 | late | 7 | 最后阶段初始化 |
arch_initcall 执行的源码对照
所有同级别的 initcall 函数通过链接脚本排在一起,执行入口在:
static void __init do_initcall_level(int level)
{
initcall_t *fn;
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(*fn);
}
而 do_initcalls() 遍历所有级别:
static void __init do_initcalls(void)
{
int level;
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level);
}
所以当 level = 3 时,就会执行 arch_initcall(customize_machine)。
关键:此时 DTB 已经解析好了
在调用 arch_initcall 之前,start_kernel() 已经执行过:
setup_arch(&command_line);
而 setup_arch() 在 ARM 上会做:
setup_processor()
↓
setup_machine_fdt(__atags_pointer)
↓
early_init_dt_scan_nodes()
↓
unflatten_device_tree()
↓
of_root 指向设备树根节点
↓
arm_memblock_init()
↓
paging_init()
所以当 arch_initcall(customize_machine) 执行时:
struct device_node 树已经准备好了
↓
of_platform_populate() 可以开始扫描
问题二:device_node 何时被添加到 platform 总线?
结论
是在 of_platform_device_create_pdata() 调用 of_device_add() 时注册的。
也就是在 arch_initcall(customize_machine) → of_platform_populate() → of_platform_bus_create() → of_platform_device_create_pdata() 链路中被添加到 platform 总线的。
添加到 platform 总线的具体位置
关键代码在:
dev->dev.bus = &platform_bus_type;
dev->dev.platform_data = platform_data;
of_dma_configure(&dev->dev, dev->dev.of_node);
if (of_device_add(dev) != 0) // ← 关键:这里注册到平台总线
goto err_clear_flag;
然后 of_device_add() 内部会调用 Linux 设备模型核心:
of_device_add()
↓
device_add()
↓
bus_add_device()
↓
platform_bus_type.devices 链表里加入这个 device
↓
bus_probe_device()
↓
device_attach()
↓
遍历 platform_bus 上已注册的 driver
↓
如果 driver_match_device() 返回成功
↓
调用 really_probe()
↓
pinctrl_bind_pins()
↓
driver->probe()
完整时间点
内核启动
│
▼
start_kernel()
│
▼
rest_init()
│
▼
kernel_init()
│
▼
kernel_init_freeable()
│
▼
do_basic_setup()
│
▼
do_initcalls()
│
▼
do_initcall_level(3) ← arch_initcall 级别
│
▼
customize_machine() ← arch_initcall(customize_machine)
│
▼
of_platform_populate() ← 从 of_root 开始扫描
│
▼
of_platform_bus_create()
│
▼
of_platform_device_create_pdata()
│ ├── 检查 status(of_device_is_available())
│ ├── 检查 OF_POPULATED 标志(防止重复)
│ ├── 分配 platform_device(platform_device_alloc())
│ ├── of_device_alloc()
│ │ ├── 转换 reg → IORESOURCE_MEM
│ │ ├── 转换 interrupts → IORESOURCE_IRQ
│ │ └── dev->dev.of_node = np
│ │
│ ├── dev->dev.bus = &platform_bus_type
│ │
│ └── of_device_add() ← 关键:此时加入 platform 总线
│ │
│ ├── device_add()
│ ├── bus_add_device() // 加到 platform_bus 的 devices 链表
│ └── bus_probe_device()
│ │
│ └── 如果 driver 已注册,立即触发 probe
│
▼
platform_device 已在 platform_bus 上
添加到总线后的后续动作
当 of_device_add() 执行后,设备已经在 platform 总线上。
此时分两种情况:
情况一:driver 已经注册
如果对应的 platform_driver 已经通过 driver_register() 注册到 platform 总线,则立即触发匹配:
device_attach()
↓
driver_match_device() ← platform_match()
↓
really_probe()
↓
pinctrl_bind_pins() ← 自动配置引脚复用
↓
driver->probe() ← 你的驱动 probe 被调用
情况二:driver 还没注册
如果驱动还是模块,还没加载,则设备会在总线上等待。
等驱动通过 module_init() → platform_driver_register() 注册上来后,才会触发匹配和 probe()。
重要区分:添加总线 vs 调用 probe
这是两个不同的时机:
| 事件 | 发生位置 | 说明 |
|---|---|---|
platform_device 添加到 platform 总线 | of_device_add() | 设备已注册,可以被 driver 匹配 |
probe() 被调用 | really_probe() | driver 和 device 匹配成功后才调用 |
大多数设备树驱动的 probe() 不是在 of_platform_populate() 时立即触发的,而是:
设备树节点 → platform_device → 在总线上等待
↓
驱动模块加载 → driver_register()
↓
driver 和 device 匹配
↓
probe() 被调用
一句话总结
arch_initcall(customize_machine)是在内核启动的do_basic_setup()→do_initcalls()链路中执行的,此时设备树已经解析成device_node树。device_node变成platform_device后,在of_platform_device_create_pdata()→of_device_add()时注册到 platform 总线。注册后如果对应驱动已加载,立即触发probe();如果驱动是模块还没加载,设备在总线上等待驱动注册后匹配。
相关源码文件
| 文件 | 作用 |
|---|---|
| init/main.c:842 | do_initcall_level() 执行各级别 initcall |
| init/main.c:857 | do_initcalls() 遍历所有 initcall 级别 |
| arch/arm/kernel/setup.c:820 | customize_machine() 注册 arch_initcall |
| drivers/of/platform.c:186 | of_device_add() 将 platform_device 注册到总线 |
| drivers/of/platform.c:435 | of_platform_populate() 扫描设备树创建设备 |
| drivers/base/platform.c:820 | platform_match() 驱动匹配函数 |
| drivers/base/dd.c:278 | really_probe() 真正调用驱动的 probe |