一、linux驱动probe调用的整体流程
1、device的创建
(1)代码流程
start_kernel // init/main.c
setup_arch(&command_line);
setup_machine_fdt(__fdt_pointer);
early_init_dt_scan(dt_virt))
early_init_dt_verify(params)
initial_boot_params = params;
unflatten_device_tree();
__unflatten_device_tree(initial_boot_params, NULL, &of_root,early_init_dt_alloc_memory_arch, false);
unflatten_dt_nodes(blob, mem, dad, mynodes);
struct device_node *root;
for (offset = 0;offset >= 0 && depth >= initial_depth;offset -= fdt_next_node(blob, offset, &depth))
populate_node(blob, offset, &mem, nps[depth],&nps[depth+1], dryrun))
pathp = fdt_get_name(blob, offset, &l);
np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl,__alignof__(struct device_node));
populate_properties(blob, offset, mem, np, pathp, dryrun);
np->name = of_get_property(np, "name", NULL);
root = nps[depth+1];
arch_initcall_sync(of_platform_default_populate_init); // drivers/of/platform.c
of_platform_default_populate_init
of_platform_default_populate(NULL, NULL, NULL);
of_platform_populate(root, of_default_bus_match_table, lookup,parent);
root = root ? of_node_get(root) : of_find_node_by_path("/");
of_find_node_by_path
of_find_node_opts_by_path
if (strcmp(path, "/") == 0)
return of_node_get(of_root);
for_each_child_of_node(root, child)
rc = of_platform_bus_create(child, matches, lookup, parent, true);
dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
struct platform_device *dev;
dev = of_device_alloc(np, bus_id, parent);
of_device_add
device_add
bus_add_device
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
(2)流程描述
从上面1的代码流程可以看出,在初始化时会解析__fdt_pointer对应地址的dtb,把整个dtb进行扫描,获取对应的数据结构并存放在of_root中
当系统调用arch_initcall_sync时,会执行of_platform_default_populate_init,在这个里面会根据of_root中的数据依次创建platform_device,并且把数据添加到klist_devices中
2、driver的创建
(1)代码流程
start_kernel // init/main.c
arch_call_rest_init
rest_init
pid = kernel_thread(kernel_init, NULL, CLONE_FS)
kernel_init_freeable
do_basic_setup
do_initcalls
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level
parse_args(initcall_level_names[level],command_line, __start___param,__stop___param - __start___param,level, level,NULL, ignore_unknown_bootoption);
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(initcall_from_entry(fn));
fn(); 【__initcall_sdhci_edge_driver_init6】
module_platform_driver(sdhci_edge_driver);
#define module_platform_driver(__platform_driver) \
module_driver(__platform_driver, platform_driver_register, \
platform_driver_unregister)
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);
platform_driver_register(sdhci_edge_driver)
__platform_driver_register(drv, THIS_MODULE)
drv->driver.bus = &platform_bus_type;
.match = platform_match,
drv->driver.probe = platform_drv_probe;
driver_register(&drv->driver);
bus_add_driver(drv)
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
if (drv->bus->p->drivers_autoprobe)
driver_attach(drv)
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
klist_iter_init_node(&bus->p->klist_devices, &i,(start ? &start->p->knode_bus : NULL));
while (!error && (dev = next_device(&i)))
error = fn(dev, data); 【__driver_attach,data就是drv】
platform_match
if (of_driver_match_device(dev, drv)) return 1
of_match_device(drv->of_match_table, dev) != NULL;
of_match_node(matches, dev->of_node);
match = __of_match_node(matches, node);
for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) {
__of_device_is_compatible(node, matches->compatible,matches->type, matches->name);
prop = __of_find_property(device, "compatible", NULL);
if (pdrv->id_table) return platform_match_id(pdrv->id_table, pdev) != NULL;
strcmp(pdev->name, id->name) == 0
return (strcmp(pdev->name, drv->name) == 0);
__driver_attach // drivers/base/dd.c
driver_match_device(drv, dev);
return drv->bus->match ? drv->bus->match(dev, drv) : 1; 【platform_match】
if (driver_allows_async_probing(drv))
async_schedule_dev(__driver_attach_async_helper, dev); 【通过创建工作队列实现异步调用】
device_driver_attach(drv, dev);
driver_probe_device
really_probe(dev, drv);
if (dev->bus->probe)
dev->bus->probe(dev);
else if (drv->probe)
drv->probe(dev); 【platform_drv_probe】
```c
async_schedule_dev
async_schedule_node(func, dev, dev_to_node(dev));
async_schedule_node_domain(func, data, node, &async_dfl_domain);
INIT_WORK(&entry->work, async_run_entry_fn);
queue_work_node(node, system_unbound_wq, &entry->work);
```c
async_run_entry_fn
entry->func(entry->data, entry->cookie); 【__driver_attach_async_helper】
__driver_attach_async_helper // drivers/base/dd.c
driver_probe_device
really_probe(dev, drv);
if (dev->bus->probe)
dev->bus->probe(dev);
else if (drv->probe)
drv->probe(dev); 【platform_drv_probe】
platform_drv_probe // drivers/base/platform.c
if (drv->probe)
drv->probe(dev); 【sdhci_edge_probe】
(2)System.map
6 ffff800010e9e8b0 d __initcall_gpio_ir_recv_driver_init6
5 ffff800010e9e8b4 d __initcall_dw_wdt_driver_init6
4 ffff800010e9e8b8 d __initcall_dt_cpufreq_platdrv_init6
3 ffff800010e9e8bc d __initcall_psci_idle_init6
2 ffff800010e9e8c0 d __initcall_mmc_pwrseq_simple_driver_init6
1 ffff800010e9e8c4 d __initcall_mmc_pwrseq_emmc_driver_init6
78355 ffff800010e9e8c8 d __initcall_mmc_blk_init6
1 ffff800010e9e8cc d __initcall_sdhci_drv_init6
2 ffff800010e9e8d0 d __initcall_sdhci_pltfm_drv_init6
3 ffff800010e9e8d4 d __initcall_sdhci_edge_driver_init6
(3)流程描述
在linux启动是会依次调用__initcall相关等级的数据,对应的就是驱动的module_init中对应的函数。在同一个等级的module_init调用顺序是受链接脚本的影响,可以参考System.map中的顺序。
当调用module_init中对应的函数时,一般会调用platform_driver_register,该接口会把对应的driver结构体挂载到klist_drivers链表中,然后继续扫描klist_devices链表,调用__driver_attach进行依次匹配,找到drv对应的device。通过platform_match进行匹配对比,也就是dtb中的compatible和驱动中的of_match_table进行比较。
如果匹配成功,且支持异步调用,则可以通过创建工作队列的方式执行async_schedule_dev进行调用触发probe函数
二、同一个等级之间的加载顺序
通过上面的”driver创建描述“,可以看出同一个等级的module_init调用顺序在System.map中可以看出来,即受链接脚本影响
三、多个相同ip之间的加载顺序
通过上面的”device创建描述“,emmc和sd卡的调用顺序,是受dtb中的顺序影响的,如果dtb中emmc靠前,则优先加载emmc。可以通过dtc继续出dts进行查看先后顺序
四、为什么mmcblkx会变化
通过上面的”driver创建描述“,在__driver_attach 的时候会判断driver_allows_async_probing,该函数是判断probe_type的类型,目前mmc的配置是PROBE_PREFER_ASYNCHRONOUS,所以支持异步调用。如果屏蔽掉的话可以解决。
五、mmcblkx的生成原理
在mmc驱动的probe函数中会调用:
host = sdhci_pltfm_init(pdev, data, sizeof(*priv));
host = sdhci_alloc_host(&pdev->dev,sizeof(struct sdhci_pltfm_host) + priv_size);
mmc = mmc_alloc_host(sizeof(struct sdhci_host) + priv_size, dev);
alias_id = of_alias_get_id(dev->of_node, "mmc");
if (alias_id >= 0) {
min_idx = alias_id;
max_idx = alias_id + 1;
} else {
min_idx = mmc_first_nonreserved_index();
max_idx = 0;
}
err = ida_simple_get(&mmc_host_ida, min_idx, max_idx, GFP_KERNEL);
ida_alloc_range(ida, start, (end) - 1, gfp)
host->index = err;
dev_set_name(&host->class_dev, "mmc%d", host->index);
sdhci_add_host(host)
sdhci_setup_host(host);
__sdhci_add_host(host);
从上面的代码可以看出,host->index就是对应的mmcblkx的编号,如果有别名,则直接使用别名,如果没有别名,则min_idx =0,max_idx =0,由于结果是end-1,也就是从0~(0-1)之间的值进行获取。
六、解决方法
1、通过别名方式绑定节点名
aliases {
mmc0 = &emmc;
mmc1 = &sd;
mmc2 = &sdio;
}
2、不使用异步方式
static struct platform_driver sdhci_edge_driver = {
.driver = {
.name = "sdhci-edge",
//.probe_type = PROBE_PREFER_ASYNCHRONOUS,
.pm = &sdhci_edge_pm_ops,
.of_match_table = sdhci_edge_match,
},
.probe = sdhci_edge_probe,
.remove = sdhci_pltfm_unregister,
}