mmcblk节点变化原因分析

文章详细阐述了Linux驱动probe调用的整体流程,从设备的创建开始,包括device树的解析、platform_device的创建,到driver的创建过程,如module_platform_driver的使用,以及platform_driver_register如何将驱动注册到总线。同时,文章讨论了同一等级module_init的调用顺序、相同IP设备的加载顺序,解释了mmcblkx的变化原因以及生成原理,并提出了通过别名绑定节点和禁用异步方式作为解决问题的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、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, 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值