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 016 #define MACH_TYPE_RISCPC 117 #define MACH_TYPE_EBSA285 418 #define MACH_TYPE_NETWINDER 519 #define MACH_TYPE_CATS 620 #define MACH_TYPE_SHARK 1521 #define MACH_TYPE_BRUTUS 1622 #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 };213214 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;916917 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 intdt_phys)205 {206 const struct machine_desc mdesc, mdesc_best = NULL;......214215 if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys)))216 return NULL;217218 mdesc = of_flat_dt_match_machine(mdesc_best,arch_get_next_mach);219......247 machine_arch_type = mdesc->nr;248249 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 voiddefault_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; 713714 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 }......739740 pr_info("Machine model: %s\n", of_flat_dt_get_machine_name());741742 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 = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>; 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 = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>; 943 clocks = <&clks IMX6UL_CLK_I2C1>;944 status = "disabled";945946 //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"; 229230 mag3110@0e {231 compatible = "fsl,mag3110";232 reg = <0x0e>;233 position = <2>;234 };235236 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 来访问节点,然后直接在里面编写要追加或者修正的内容。