每个节点都有 compatible 属性,根节点“/”也不例外,imx6ull-alientek-emmc.dts 文件中根节点的 compatible 属性内容如下所示:
14 / { 15 model = "Freescale i.MX6 ULL 14x14 EVK Board"; 16 compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull"; ...... 148 }
可以看出,compatible 有两个值:“fsl,imx6ull-14x14-evk”和“fsl,imx6ull”。前面我们说了,设备节点的compatible 属性值是为了匹配Linux 内核中的驱动程序,那么根节点中的 compatible属性是为了做什么工作的? 通过根节点的compatible 属性可以知道我们所使用的设备,一般第一个值描述了所使用的硬件设备名字,比如这里使用的是“imx6ull-14x14-evk”这个设备,第二个值描述了设备所使用的SOC,比如这里使用的是“imx6ull”这颗 SOC。Linux 内核会通过根节点的 compoatible 属性查看是否支持此设备,如果支持的话设备就会启动 Linux 内核。接下来我们就来学习一下Linux 内核在使用设备树前后是如何判断是否支持某款设备的。
1、使用设备树之前设备匹配方法
在没有使用设备树以前,uboot 会向 Linux 内核传递一个叫做 machine id 的值,machine id也就是设备 ID,告诉 Linux 内核自己是个什么设备,看看 Linux 内核是否支持。Linux 内核是支持很多设备的,针对每一个设备(板子),Linux 内核都用MACHINE_START 和MACHINE_END来定义一个 machine_desc 结构体来描述这个设备, 比如在文件 arch/arm/mach-imx/mach-mx35_3ds.c 中有如下定义:
613 MACHINE_START(MX35_3DS, "Freescale MX35PDK") 614 /* Maintainer: Freescale Semiconductor, Inc */ 615 .atag_offset = 0x100, 616 .map_io = mx35_map_io, 617 .init_early = imx35_init_early, 618 .init_irq = mx35_init_irq, 619 .init_time = mx35pdk_timer_init, 620 .init_machine = mx35_3ds_init, 621 .reserve = mx35_3ds_reserve, 622 .restart = mxc_restart, 623 MACHINE_END
上述代码就是定义了“ Freescale MX35PDK ” 这个设备, 其中 MACHINE_START 和MACHINE_END 定义在文件 arch/arm/include/asm/mach/arch.h 中,内容如下:
#define MACHINE_START(_type,_name) \ static const struct machine_desc mach_desc_##_type \ used \ attribute (( section (".arch.info.init"))) = { \ .nr = MACH_TYPE_##_type, \ .name = _name, #define MACHINE_END \ };
根据 MACHINE_START 和 MACHINE_END 的宏定义,将代码展开后如下所示:
1 static const struct machine_desc mach_desc_MX35_3DS \ 2 used \ 3 attribute (( section (".arch.info.init"))) = { 4 .nr = MACH_TYPE_MX35_3DS, 5 .name = "Freescale MX35PDK", 6 /* Maintainer: Freescale Semiconductor, Inc */ 7 .atag_offset = 0x100, 8 .map_io = mx35_map_io, 9 .init_early = imx35_init_early, 10 .init_irq = mx35_init_irq, 11 .init_time = mx35pdk_timer_init, 12 .init_machine = mx35_3ds_init, 13 .reserve = mx35_3ds_reserve, 14 .restart = mxc_restart, 15 }
从代码中可以看出,这里定义了一个 machine_desc 类型的结构体变量mach_desc_MX35_3DS , 这 个 变 量 存 储 在 “ .arch.info.init ” 段 中 。 第 4 行的 MACH_TYPE_MX35_3DS 就 是 “ Freescale MX35PDK ” 这 个 板 子 的 machine id 。MACH_TYPE_MX35_3DS 定义在文件 include/generated/mach-types.h 中,此文件定义了大量的machine id,内容如下所示:
15 #define MACH_TYPE_EBSA110 0 16 #define MACH_TYPE_RISCPC 1 17 #define MACH_TYPE_EBSA285 4 18 #define MACH_TYPE_NETWINDER 5 19 #define MACH_TYPE_CATS 6 20 #define MACH_TYPE_SHARK 15 21 #define MACH_TYPE_BRUTUS 16 22 #define MACH_TYPE_PERSONAL_SERVER 17 ...... 287 #define MACH_TYPE_MX35_3DS 1645 ...... 1000 #define MACH_TYPE_PFLA03 4575
第 287 行就是 MACH_TYPE_MX35_3DS 的值,为 1645。
前面说了,uboot 会给 Linux 内核传递 machine id 这个参数,Linux 内核会检查这个 machineid,其实就是将 machine id 与示例代码中的这些 MACH_TYPE_XXX 宏进行对比,看看有没有相等的,如果相等的话就表示 Linux 内核支持这个设备,如果不支持的话那么这个设备就没法启动Linux 内核。
2、使用设备树以后的设备匹配方法
当 Linux 内核引入设备树以后就不再使用 MACHINE_START 了, 而是换为了DT_MACHINE_START。DT_MACHINE_START 也定义在文件arch/arm/include/asm/mach/arch.h里面,定义如下:
#define DT_MACHINE_START(_name, _namestr) \ static const struct machine_desc mach_desc_##_name \ used \ attribute (( section (".arch.info.init"))) = { \ .nr = ~0, \ .name = _namestr,
可以看出,DT_MACHINE_START 和 MACHINE_START 基本相同,只是.nr 的设置不同,在 DT_MACHINE_START 里面直接将.nr 设置为~0。说明引入设备树以后不会再根据 machineid 来检查 Linux 内核是否支持某个设备了。
打开文件 arch/arm/mach-imx/mach-imx6ul.c,有如下所示内容:
208 static const char *imx6ul_dt_compat[] initconst = { 209 "fsl,imx6ul", 210 "fsl,imx6ull", 211 NULL, 212 }; 213 214 DT_MACHINE_START(IMX6UL, "Freescale i.MX6 Ultralite (Device Tree)") 215 .map_io = imx6ul_map_io, 216 .init_irq = imx6ul_init_irq, 217 .init_machine = imx6ul_init_machine, 218 .init_late = imx6ul_init_late, 219 .dt_compat = imx6ul_dt_compat, 220 MACHINE_END
machine_desc 结构体中有个.dt_compat 成员变量,此成员变量保存着本设备兼容属性,示例代码 中设置.dt_compat = imx6ul_dt_compat,imx6ul_dt_compat 表里面有"fsl,imx6ul"和"fsl,imx6ull" 这两个兼容值。只要某个设备( 板子) 根节点“/”的 compatible 属性值与imx6ul_dt_compat 表中的任何一个值相等,那么就表示 Linux 内核支持此设备。imx6ull-alientek-emmc.dts 中根节点的 compatible 属性值如下:
compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
其中“fsl,imx6ull”与 imx6ul_dt_compat 中的“fsl,imx6ull”匹配,因此 I.MX6U-ALPHA 开发板可以正常启动Linux 内核。如果将 imx6ull-alientek-emmc.dts 根节点的compatible 属性改为其他的值,比如:compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ullll"重新编译DTS,并用新的DTS 启动 Linux 内核,结果如图所示的错误提示:
当我们修改了根节点 compatible 属性内容以后,因为 Linux 内核找不到对应的设备,因此Linux 内核无法启动。在 uboot 输出 Starting kernel…以后就再也没有其他信息输出了。接下来我们简单看一下 Linux 内核是如何根据设备树根节点的 compatible 属性来匹配出对应的 machine_desc,Linux 内核调用 start_kernel 函数来启动内核,start_kernel 函数会调用setup_arch 函数来匹配 machine_desc,setup_arch 函数定义在文件 arch/arm/kernel/setup.c 中,函数内容如下(有缩减):
913 void init setup_arch(char **cmdline_p) 914 { 915 const struct machine_desc *mdesc; 916 917 setup_processor(); 918 mdesc = setup_machine_fdt( atags_pointer); 919 if (!mdesc) 920 mdesc = setup_machine_tags( atags_pointer, machine_arch_type); 921 machine_desc = mdesc; 922 machine_name = mdesc->name; ...... 986 }
第 918 行,调用 setup_machine_fdt 函数来获取匹配的 machine_desc,参数就是 atags 的首地址,也就是 uboot 传递给 Linux 内核的dtb 文件首地址,setup_machine_fdt 函数的返回值就是找到的最匹配的machine_desc。
函数 setup_machine_fdt 定义在文件 arch/arm/kernel/devtree.c 中,内容如下(有缩减):
204 const struct machine_desc * init setup_machine_fdt(unsigned int dt_phys) 205 { 206 const struct machine_desc *mdesc, *mdesc_best = NULL; ...... 214 215 if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys))) 216 return NULL; 217 218 mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach); 219 ...... 247 machine_arch_type = mdesc->nr; 248 249 return mdesc; 250 }
第 218 行,调用函数 of_flat_dt_match_machine 来获取匹配的machine_desc,参数 mdesc_best是默认的 machine_desc ,参数 arch_get_next_mach 是个函数, 此函数定义在定义在arch/arm/kernel/devtree.c 文件中。找到匹配的 machine_desc 的过程就是用设备树根节点的compatible 属性值和 Linux 内核中保存的所以 machine_desc 结构的. dt_compat 中的值比较,看看那个相等,如果相等的话就表示找到匹配的 machine_desc,arch_get_next_mach 函数的工作就是获取 Linux 内核中下一个 machine_desc 结构体。
最后在来看一下 of_flat_dt_match_machine 函数,此函数定义在文件 drivers/of/fdt.c 中,内容如下(有缩减):
705 const void * init of_flat_dt_match_machine(const void *default_match, 706 const void * (*get_next_compat)(const char * const**)) 707 { 708 const void *data = NULL; 709 const void *best_data = default_match; 710 const char *const *compat; 711 unsigned long dt_root; 712 unsigned int best_score = ~1, score = 0; 713 714 dt_root = of_get_flat_dt_root(); 715 while ((data = get_next_compat(&compat))) { 716 score = of_flat_dt_match(dt_root, compat); 717 if (score > 0 && score < best_score) { 718 best_data = data; 719 best_score = score; 720 } 721 } ...... 739 740 pr_info("Machine model: %s\n", of_flat_dt_get_machine_name()); 741 742 return best_data; 743 }
第 714 行,通过函数 of_get_flat_dt_root 获取设备树根节点。
第 715~720 行,此循环就是查找匹配的 machine_desc 过程,第 716 行的 of_flat_dt_match 函数会将根节点compatible 属性的值和每个machine_desc 结构体中. dt_compat 的值进行比较,直至找到匹配的那个 machine_desc。
总结一下,Linux 内核通过根节点 compatible 属性找到对应的设备的函数调用过程,如图所示:
产品开发过程中可能面临着频繁的需求更改,比如第一版硬件上有一个 IIC 接口的六轴芯片 MPU6050,第二版硬件又要把这个 MPU6050 更换为 MPU9250 等。一旦硬件修改了,我们就要同步的修改设备树文件,毕竟设备树是描述板子硬件信息的文件。假设现在有个六轴芯片fxls8471,fxls8471 要接到 I.MX6U-ALPHA 开发板的 I2C1 接口上,那么相当于需要在 i2c1 这个节点上添加一个 fxls8471 子节点。先看一下 I2C1 接口对应的节点,打开文件 imx6ull.dtsi 文件,找到如下所示内容:
937 i2c1: i2c@021a0000 { 938 #address-cells = <1>; 939 #size-cells = <0>; 940 compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c"; 941 reg = <0x021a0000 0x4000>; 942 interrupts =; 943 clocks = <&clks IMX6UL_CLK_I2C1>; 944 status = "disabled"; 945 };
上面代码就是 I.MX6ULL 的 I2C1 节点,现在要在 i2c1 节点下创建一个子节点,这个子节点就是 fxls8471,最简单的方法就是在 i2c1 下直接添加一个名为 fxls8471 的子节点,如下所示:
937 i2c1: i2c@021a0000 { 938 #address-cells = <1>; 939 #size-cells = <0>; 940 compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c"; 941 reg = <0x021a0000 0x4000>; 942 interrupts =; 943 clocks = <&clks IMX6UL_CLK_I2C1>; 944 status = "disabled"; 945 946 //fxls8471 子节点 947 fxls8471@1e { 948 compatible = "fsl,fxls8471"; 949 reg = <0x1e>; 950 }; 951 };
第 947~950 行就是添加的 fxls8471 这个芯片对应的子节点。但是这样会有个问题!i2c1 节点是定义在 imx6ull.dtsi 文件中的,而 imx6ull.dtsi 是设备树头文件,其他所有使用到 I.MX6ULL这颗 SOC 的板子都会引用 imx6ull.dtsi 这个文件。直接在 i2c1 节点中添加 fxls8471 就相当于在其他的所有板子上都添加了 fxls8471 这个设备,但是其他的板子并没有这个设备啊!因此,按照示例代码这样写肯定是不行的。
这里就要引入另外一个内容,那就是如何向节点追加数据,我们现在要解决的就是如何向i2c1 节点追加一个名为 fxls8471 的子节点,而且不能影响到其他使用到 I.MX6ULL 的板子。 I.MX6U-ALPHA 开发板使用的设备树文件为 imx6ull-alientek-emmc.dts, 因此我们需要在 imx6ull-alientek-emmc.dts 文件中完成数据追加的内容,方式如下:
1 &i2c1 { 2 /* 要追加或修改的内容 */ 3 };
第 1 行,&i2c1 表示要访问 i2c1 这个 label 所对应的节点,也就是 imx6ull.dtsi 中的“i2c1: i2c@021a0000”。
第 2 行,花括号内就是要向 i2c1 这个节点添加的内容,包括修改某些属性的值。打开 imx6ull-alientek-emmc.dts,找到如下所示内容:
224 &i2c1 { 225 clock-frequency = <100000>; 226 pinctrl-names = "default"; 227 pinctrl-0 = <&pinctrl_i2c1>; 228 status = "okay"; 229 230 mag3110@0e { 231 compatible = "fsl,mag3110"; 232 reg = <0x0e>; 233 position = <2>; 234 }; 235 236 fxls8471@1e { 237 compatible = "fsl,fxls8471"; 238 reg = <0x1e>; 239 position = <0>; 240 interrupt-parent = <&gpio5>; 241 interrupts = <0 8>; 242 }; 243 };
代码就是向 i2c1 节点添加/修改数据,比如第 225 行的属性“clock-frequency”就表示 i2c1 时钟为 100KHz。“clock-frequency”就是新添加的属性。
第 228 行,将 status 属性的值由原来的disabled 改为 okay。
第 230~234 行,i2c1 子节点 mag3110,因为 NXP 官方开发板在 I2C1 上接了一个磁力计芯片 mag3110,正点原子的 I.MX6U-ALPHA 开发板并没有使用 mag3110。
第 236~242 行,i2c1 子节点 fxls8471,同样是因为 NXP 官方开发板在 I2C1 上接了 fxls8471这颗六轴芯片。
因为代码中的内容是 imx6ull-alientek-emmc.dts 这个文件内的,所以不会对使用 I.MX6ULL 这颗SOC 的其他板子造成任何影响。这个就是向节点追加或修改内容,重点就是通过&label 来访问节点,然后直接在里面编写要追加或者修改的内容。