首页 » 互联网 » Linux设备树专有名词及语律例则详解(下)_节点_装备

Linux设备树专有名词及语律例则详解(下)_节点_装备

南宫静远 2024-12-07 01:49:57 0

扫一扫用手机浏览

文章目录 [+]

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、利用设备树之前设备匹配方法

Linux设备树专有名词及语律例则详解(下)_节点_装备 Linux设备树专有名词及语律例则详解(下)_节点_装备 互联网

在没有利用设备树以前,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 中有如下定义:

Linux设备树专有名词及语律例则详解(下)_节点_装备 Linux设备树专有名词及语律例则详解(下)_节点_装备 互联网
(图片来自网络侵删)

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 来访问节点,然后直接在里面编写要追加或者修正的内容。

标签:

相关文章