上面简要的了解一丢丢的属性,其实也最常用的三个属性。
但是还是应该系统的学学啦。开整。
参考资料:《Linux设备驱动开发详解》
1、根节点兼容性
上述.dts文件中,第2行根节点"/"的兼容属性compatible=“acme,coyotes-revenge”;定义了整个系统(设备级别)的名称,它的组织形式为:,。
Linux内核通过根节点"/"的兼容属性即可判断它启动的是什么设备。
在真实项目中,这个顶层设备的兼容属性一般包括两个或者两个以上的兼容性字符串,首个兼容性字符串是板子级别的名字,后面一个兼容性是芯片级别(或者芯片系列级别)的名字。
譬如板子arch/arm/boot/dts/vexpress-v2p-ca9.dts兼容于arm,vexpress,v2p-ca9和“arm,vexpress”:
compatible = "arm,vexpress,v2p-ca9", "arm,vexpress";
板子arch/arm/boot/dts/vexpress-v2p-ca5s.dts的兼容性则为:
compatible = "arm,vexpress,v2p-ca5s", "arm,vexpress";
板子arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts的兼容性为:
compatible = "arm,vexpress,v2p-ca15_a7", "arm,vexpress";
可以看出,上述各个电路板的共性是兼容于arm,vexpress,而特性是分别兼容于arm,vexpress,v2p-ca9、arm,vexpress,v2p-ca5s和arm,vexpress,v2p-ca15_a7。
进一步地看,arch/arm/boot/dts/exynos4210-origen.dts的兼容性字段如下:
compatible = "insignal,origen", "samsung,exynos4210", "samsung,exynos4";
第一个字符串是板子名字(很特定),第2个字符
串是芯片名字(比较特定),第3个字段是芯片系列的名字(比较通用)。
作为类比,arch/arm/boot/dts/exynos4210-universal_c210.dts的兼容性字段则如下:
compatible = "samsung,universal_c210", "samsung,exynos4210", "samsung,exynos4";
由此可见,它与exynos4210-origen.dts的区别只在于第1个字符串(特定的板子名字)不一样,后面芯片名和芯片系列的名字都一样。
在Linux 2.6内核中,ARM Linux针对不同的电路板会建立由MACHINE_START和MACHINE_END包围起来的针对这个设备的一系列回调函数,如代码清单18.3所示。
1 MACHINE_START(VEXPRESS, "ARM-Versatile Express") 2 .atag_offset = 0x100, 3 .smp = smp_ops(vexpress_smp_ops), 4 .map_io = v2m_map_io, 5 .init_early = v2m_init_early, 6 .init_irq = v2m_init_irq, 7 .timer = &v2m_timer, 8 .handle_irq = gic_handle_irq, 9 .init_machine = v2m_init, 10 .restart = vexpress_restart, 11 MACHINE_END
这些不同的设备会有不同的MACHINE ID,Uboot在启动Linux内核时会将MACHINE ID存放在r1寄存器,Linux启动时会匹配Bootloader传递的MACHINE ID和MACHINE_START声明的MACHINE ID,然后执行相应设备的一系列初始化函数。
ARM Linux 3.x在引入设备树之后,MACHINE_START变更为DT_MACHINE_START,其中含有一个.dt_compat成员,用于表明相关的设备与.dts中根节点的兼容属性兼容关系。
如果Bootloader传递给内核的设备树中根节点的兼容属性出现在某设备的.dt_compat表中,相关的设备就与对应的兼容匹配,从而引发这一设备的一系列初始化函数被执行。一个典型的DT_MACHINE如代码清单18.4所示。
1 static const char * const v2m_dt_match[] __initconst = { 2 "arm,vexpress", 3 "xen,xenvm", 4 NULL, 5 }; 6 DT_MACHINE_START(VEXPRESS_DT, "ARM-Versatile Express") 7 .dt_compat = v2m_dt_match, 8 .smp = smp_ops(vexpress_smp_ops), 9 .map_io = v2m_dt_map_io, 10 .init_early = v2m_dt_init_early, 11 .init_irq = v2m_dt_init_irq, 12 .timer = &v2m_dt_timer, 13 .init_machine = v2m_dt_init, 14 .handle_irq = gic_handle_irq, 15 .restart = vexpress_restart, 16 MACHINE_END
Linux倡导针对多个SoC、多个电路板的通用DT设备,即一个DT设备的.dt_compat包含多个电路板.dts文件的根节点兼容属性字符串。 (举个栗子就是这个流程 这个那个板子都可以用)
之后,如果这多个电路板的初始化序列不一样,可以通过int of_machine_is_compatible(const char*compat)API判断具体的电路板是什么。在Linux内核中,常常使用如下API来判断根节点的兼容性:
int of_machine_is_compatible(const char *compat);
此API判断目前运行的板子或者SoC的兼容性,它匹配的是设备树根节点下的兼容属性。
例如drivers/cpufreq/exynos-cpufreq.c中就有判断运行的CPU类型是exynos4210、exynos4212、exynos4412还是exynos5250的代码,进而分别处理,如代码清单18.5所示。
1 static int exynos_cpufreq_probe(struct platform_device *pdev) 2 { 3 int ret = -EINVAL; 4 5 exynos_info = kzalloc(sizeof(*exynos_info), GFP_KERNEL); 6 if (!exynos_info) 7 return -ENOMEM; 8 9 exynos_info->dev = &pdev->dev; 10 11 if (of_machine_is_compatible("samsung,exynos4210")) { 12 exynos_info->type = EXYNOS_SOC_4210; 13 ret = exynos4210_cpufreq_init(exynos_info); 14 } else if (of_machine_is_compatible("samsung,exynos4212")) { 15 exynos_info->type = EXYNOS_SOC_4212; 16 ret = exynos4x12_cpufreq_init(exynos_info); 17 } else if (of_machine_is_compatible("samsung,exynos4412")) { 18 exynos_info->type = EXYNOS_SOC_4412; 19 ret = exynos4x12_cpufreq_init(exynos_info); 20 } else if (of_machine_is_compatible("samsung,exynos5250")) { 21 exynos_info->type = EXYNOS_SOC_5250; 22 ret = exynos5250_cpufreq_init(exynos_info); 23 } else { 24 pr_err("%s: Unknown SoC type\n", __func__); 25 return -ENODEV; 26 } 27 ... 28 }
如果一个兼容包含多个字符串,譬如对于前面介绍的根节点兼容compatible=“samsung,universal_c210”,“samsung,exynos4210”,"samsung,exynos4"的情况,如下3个表达式都是成立的。
of_machine_is_compatible("samsung,universal_c210") of_machine_is_compatible("samsung,exynos4210") of_machine_is_compatible("samsung,exynos4")
这个的复用吧,我觉得是利用一套去适应多条的必然,但是如果相对于本身,顺序或者自定义过多,那么其实也没有必要复用这个DT吧。具体的当然我现在的理解都是纯靠猜想,具体等到设计到的时候,再做详细展开,正如我所说,学习读书,联接很重要。