-
2021-07-30 08:17:56
设备树知识点
注意事项
编写设备树文件,一定要保证引脚没有被占用。
设备树
dts (device tree source) 描述SOC外部外设信息的设备树文件。
dtsi (device tree source include) 描述SOC内部外设信息的设备树文件,可以被包含到其他.dts文件中。
dtb (device tree blob) 经编译工具生成的设备树二进制文件
dtc (device tree compiler) 设备树编译工具
在Linux系统终端下使用make dtbs命令,来编译设备树文件,生成dtb文件。设备树节点命名格式:
节点命名格式: 节点标签:节点名称@设备地址,可以通过&节点标签来直接访问这个节点。
设备树结构:设备树+设备树节点追加内容
设备树是一个包含节点和属性的简单树状结构。属性就是键值对,而节点可以同时包含属性和子节点。
/* * Copyright (C) 2016 Freescale Semiconductor, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ /* 设备树 */ / { model = "Freescale i.MX6 ULL 14x14 EVK Board"; compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull"; chosen { stdout-path = &uart1; }; memory { reg = <0x80000000 0x20000000>; }; /* Linux LED driver*/ led { compatible = "gpio-leds"; led0{ label = "red_led"; gpios = <&gpio1 3 GPIO_ACTIVE_LOW>; status = "okay"; }; }; beep { compatible = "myboard-beep"; label = "red"; beep-gpios = <&gpio5 1 GPIO_ACTIVE_LOW>; status = "okay"; }; key { #address-cells = <1>; #size-cells = <1>; label = "yellow_key"; compatible = "myboard-key"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_key>; gpios = <&gpio1 18 GPIO_ACTIVE_LOW>; interrupt-parent = <&gpio1>; interrupts = <18 IRQ_TYPE_EDGE_BOTH>; status = "okay"; }; }; /* 设备树节点追加内容 */ &iomuxc { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_hog_1>; imx6ul-evk { pinctrl_led: ledgrp { fsl,pins = < MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x30b0 >; }; pinctrl_key: keygrp { fsl,pins = < MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xf080 >; }; pinctrl_ecspi3: icm20608 { fsl,pins = < MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x10b0 /* CS */ MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x10b1 /* SCLK */ MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x10b1 /* MISO */ MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x10b1 /* MOSI */ >; }; }; }; &ecspi3 { fsl,spi-num-chipselects = <1>; cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>; /* cant't use cs-gpios! */ pinctrl-names = "default"; pinctrl-0 = <&pinctrl_ecspi3>; status = "okay"; spidev: icm20608@0 { compatible = "myboard,icm20608"; spi-max-frequency = <8000000>; reg = <0>; }; }; &wdog1 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_wdog>; fsl,wdog_b; };
该设备树包括:
一个单独的根节点 /
两个子节点 node1 node2
两个子节点的子节点 child-node1 child-node2
一堆分散在树里的属性设备树的基本单元是node,这些node被组织成树形。
设备树内容都是以key-value键值对的形式存在的。设备树节点和属性
节点命名格式:节点标签:节点名称@设备地址 model: chosen: memory: aliases节点: 格式:property = &label; 这与之前的phandle = <&label>;形式不同,这是把一个phandle值插入到一个cell。 compatible = "ovti,ov9650"; compatible:该字符串的格式是<制造商>,<型号>。 compatible:兼容性属性,用来决定操作系统使用哪个设备驱动来绑定设备。 status = "okay"/"disabled"; status:状态属性 GPIO: gpios = <&gpio5 9 GPIO_ACTIVE_LOW>; gpios = <&gpio4 22 1>; cs-gpios = <&gpio5 7 0>; &usdhc1 { pinctrl-names = "default", "state_100mhz", "state_200mhz"; pinctrl-0 = <&pinctrl_usdhc1>; pinctrl-1 = <&pinctrl_usdhc1_100mhz>; pinctrl-2 = <&pinctrl_usdhc1_200mhz>; }; pinctrl-names = "default", "state_100mhz", "state_200mhz";定义了设备有三种状态。 pinctrl-0,pinctrl-1,pinctrl-2:定义了该设备状态的对应引脚。 范围(地址转换): ranges: 中断: interrupt-parent = <&gpio1>; interrupt-parent:这是一个设备节点的属性。包含一个指向该设备连接的中断控制器的phandle。那些没有interrupt-parent的节点则从它们的父节点中继承该属性。 interrupt-controller:中断控制器。一个空的属性定义表示该节点作为一个接收中断信号的设备。 #interrupt-cells:这是一个中断控制器节点的属性。它声明了该中断控制器的中断指示符中cell的个数(类似于#address-cells和#size-cells)。 interrupts = <18 IRQ_TYPE_EDGE_BOTH>; interrupts:中断输入线。包含一个中断指示符的列表,对应于该设备上的每个中断输出信号。 interrupt-names:为interrupts属性中的每个中断指定一个名称。 其他属性: clock-names:clocks属性中命名clocks。 dma-names:用于dma属性。
父节点属性: #address-cells:属性值决定了子节点reg属性中地址信息所占用的字长(32 位)。(地址) #size-cells:属性值决定了子节点reg属性中长度信息所占的字长(32 位)。(长度) 子节点属性: reg = <0x80000000 0x20000000>; reg:该属性的格式是<[地址1 长度1][地址2 长度2][地址3 长度3]...> reg:如果一个节点有reg属性,那么该节点的名字就必须包含设备地址,这个设备地址就是reg属性里第一个地址值。 reg:reg属性一般用于描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息。 reg-names:reg属性中的内存区域列表。 cpu节点: #address-cells = <1>; #size-cells = <0>; reg = <0>; 内存映射设备: #address-cells = <1>; #size-cells = <1>; reg = <0x10100000 0x1000>; //与cpu节点里单一地址值不同,应该分配给内存映射设备一个地址范围。32位机器,设置每个地址值为1cell,每个长度值也是1cell。64位机器,设置每个地址值为2cell,每个长度值也是2cell。 带独立片选线的设备: #address-cells = <2>; #size-cells = <1>; reg = <1 0 0x1000>; reg = <片选号 偏移量 长度>; 非内存映射设备: #address-cells = <1>; #size-cells = <0>; spi@58{ reg = <58>; };
更多相关内容 -
Linux 设备树(三) 设备树语法
2022-03-12 09:22:22设备树就像C语言一样,C语言有自己的语法特点,设备树也有自己的语法特点,接下来就来简单了解一些设备树语法。 1、头文件包含 设备树跟C语言一样,可以包含其他的设备树文件,其语法是使用#include进行头文件包含...设备树就像C语言一样,C语言有自己的语法特点,设备树也有自己的语法特点,接下来就来简单了解一些设备树语法。
- 1、头文件包含
设备树跟C语言一样,可以包含其他的设备树文件,其语法是使用#include
进行头文件包含。
#include <dt-bindings/input/input.h> #include "imx6ull.dtsi"
头文件包含有两种方式,一种是尖括号<>,代表在系统默认目录下进行寻找。一种是双引号"",代表在当前目录下进行寻找。
一般来说xxx.dtsi文件是用来描述CPU内部外设的一些基本信息,拿imx6ull.dtsi来举例#include <dt-bindings/clock/imx6ul-clock.h> #include <dt-bindings/gpio/gpio.h> #include <dt-bindings/interrupt-controller/arm-gic.h> #include "imx6ull-pinfunc.h" #include "imx6ull-pinfunc-snvs.h" #include "skeleton.dtsi" / { aliases { can0 = &flexcan1; can1 = &flexcan2; ethernet0 = &fec1; ethernet1 = &fec2; gpio0 = &gpio1; gpio1 = &gpio2; gpio2 = &gpio3; gpio3 = &gpio4; ........................... }; cpus { #address-cells = <1>; #size-cells = <0>; cpu0: cpu@0 { compatible = "arm,cortex-a7"; device_type = "cpu"; reg = <0>; clock-latency = <61036>; /* two CLK32 periods */ operating-points = < /* kHz uV */ 996000 1275000 792000 1225000 528000 1175000 396000 1025000 198000 950000 >; fsl,soc-operating-points = < /* KHz uV */ 996000 1175000 792000 1175000 528000 1175000 396000 1175000 198000 1175000 >; fsl,low-power-run; clocks = <&clks IMX6UL_CLK_ARM>, <&clks IMX6UL_CLK_PLL2_BUS>, <&clks IMX6UL_CLK_PLL2_PFD2>, <&clks IMX6UL_CA7_SECONDARY_SEL>, <&clks IMX6UL_CLK_STEP>, <&clks IMX6UL_CLK_PLL1_SW>, <&clks IMX6UL_CLK_PLL1_SYS>, <&clks IMX6UL_PLL1_BYPASS>, <&clks IMX6UL_CLK_PLL1>, <&clks IMX6UL_PLL1_BYPASS_SRC>, <&clks IMX6UL_CLK_OSC>; clock-names = "arm", "pll2_bus", "pll2_pfd2_396m", "secondary_sel", "step", "pll1_sw", "pll1_sys", "pll1_bypass", "pll1", "pll1_bypass_src", "osc"; }; }; intc: interrupt-controller@00a01000 { compatible = "arm,cortex-a7-gic"; #interrupt-cells = <3>; interrupt-controller; reg = <0x00a01000 0x1000>, <0x00a02000 0x100>; }; clocks { #address-cells = <1>; #size-cells = <0>; ckil: clock@0 { compatible = "fixed-clock"; reg = <0>; #clock-cells = <0>; clock-frequency = <32768>; clock-output-names = "ckil"; }; osc: clock@1 { compatible = "fixed-clock"; reg = <1>; #clock-cells = <0>; clock-frequency = <24000000>; clock-output-names = "osc"; }; ipp_di0: clock@2 { compatible = "fixed-clock"; reg = <2>; #clock-cells = <0>; clock-frequency = <0>; clock-output-names = "ipp_di0"; }; ipp_di1: clock@3 { compatible = "fixed-clock"; reg = <3>; #clock-cells = <0>; clock-frequency = <0>; clock-output-names = "ipp_di1"; }; }; soc { #address-cells = <1>; #size-cells = <1>; compatible = "simple-bus"; interrupt-parent = <&gpc>; ranges; busfreq { compatible = "fsl,imx_busfreq"; clocks = <&clks IMX6UL_CLK_PLL2_PFD2>, <&clks IMX6UL_CLK_PLL2_198M>, <&clks IMX6UL_CLK_PLL2_BUS>, <&clks IMX6UL_CLK_ARM>, <&clks IMX6UL_CLK_PLL3_USB_OTG>, <&clks IMX6UL_CLK_PERIPH>, <&clks IMX6UL_CLK_PERIPH_PRE>, <&clks IMX6UL_CLK_PERIPH_CLK2>, <&clks IMX6UL_CLK_PERIPH_CLK2_SEL>, <&clks IMX6UL_CLK_OSC>, <&clks IMX6UL_CLK_AHB>, <&clks IMX6UL_CLK_AXI>, <&clks IMX6UL_CLK_PERIPH2>, <&clks IMX6UL_CLK_PERIPH2_PRE>, <&clks IMX6UL_CLK_PERIPH2_CLK2>, <&clks IMX6UL_CLK_PERIPH2_CLK2_SEL>, <&clks IMX6UL_CLK_STEP>, <&clks IMX6UL_CLK_MMDC_P0_FAST>, <&clks IMX6UL_PLL1_BYPASS_SRC>, <&clks IMX6UL_PLL1_BYPASS>, <&clks IMX6UL_CLK_PLL1_SYS>, <&clks IMX6UL_CLK_PLL1_SW>, <&clks IMX6UL_CLK_PLL1>; clock-names = "pll2_pfd2_396m", "pll2_198m", "pll2_bus", "arm", "pll3_usb_otg", "periph", "periph_pre", "periph_clk2", "periph_clk2_sel", "osc", "ahb", "ocram", "periph2", "periph2_pre", "periph2_clk2", "periph2_clk2_sel", "step", "mmdc", "pll1_bypass_src", "pll1_bypass", "pll1_sys", "pll1_sw", "pll1"; fsl,max_ddr_freq = <400000000>; }; pmu { compatible = "arm,cortex-a7-pmu"; interrupts = <GIC_SPI 94 IRQ_TYPE_LEVEL_HIGH>; status = "disabled"; }; ocrams: sram@00900000 { compatible = "fsl,lpm-sram"; reg = <0x00900000 0x4000>; }; ocrams_ddr: sram@00904000 { compatible = "fsl,ddr-lpm-sram"; reg = <0x00904000 0x1000>; }; ocram: sram@00905000 { compatible = "mmio-sram"; reg = <0x00905000 0x1B000>; }; dma_apbh: dma-apbh@01804000 { compatible = "fsl,imx6ul-dma-apbh", "fsl,imx28-dma-apbh"; reg = <0x01804000 0x2000>; interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>; interrupt-names = "gpmi0", "gpmi1", "gpmi2", "gpmi3"; #dma-cells = <1>; dma-channels = <4>; clocks = <&clks IMX6UL_CLK_APBHDMA>; }; gpmi: gpmi-nand@01806000{ compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpmi-nand"; #address-cells = <1>; #size-cells = <1>; reg = <0x01806000 0x2000>, <0x01808000 0x4000>; reg-names = "gpmi-nand", "bch"; interrupts = <GIC_SPI 15 IRQ_TYPE_LEVEL_HIGH>; interrupt-names = "bch"; clocks = <&clks IMX6UL_CLK_GPMI_IO>, <&clks IMX6UL_CLK_GPMI_APB>, <&clks IMX6UL_CLK_GPMI_BCH>, <&clks IMX6UL_CLK_GPMI_BCH_APB>, <&clks IMX6UL_CLK_PER_BCH>; clock-names = "gpmi_io", "gpmi_apb", "gpmi_bch", "gpmi_bch_apb", "per1_bch"; dmas = <&dma_apbh 0>; dma-names = "rx-tx"; status = "disabled"; }; .............................. tsc: tsc@02040000 { compatible = "fsl,imx6ul-tsc"; reg = <0x02040000 0x4000>, <0x0219c000 0x4000>; interrupts = <GIC_SPI 3 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 101 IRQ_TYPE_LEVEL_HIGH>; clocks = <&clks IMX6UL_CLK_IPG>, <&clks IMX6UL_CLK_ADC2>; clock-names = "tsc", "adc"; status = "disabled"; }; pwm1: pwm@02080000 { compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm"; reg = <0x02080000 0x4000>; interrupts = <GIC_SPI 83 IRQ_TYPE_LEVEL_HIGH>; clocks = <&clks IMX6UL_CLK_PWM1>, <&clks IMX6UL_CLK_PWM1>; clock-names = "ipg", "per"; #pwm-cells = <2>; }; pwm2: pwm@02084000 { compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm"; reg = <0x02084000 0x4000>; interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>; clocks = <&clks IMX6UL_CLK_DUMMY>, <&clks IMX6UL_CLK_DUMMY>; clock-names = "ipg", "per"; #pwm-cells = <2>; }; pwm3: pwm@02088000 { compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm"; reg = <0x02088000 0x4000>; interrupts = <GIC_SPI 85 IRQ_TYPE_LEVEL_HIGH>; clocks = <&clks IMX6UL_CLK_PWM3>, <&clks IMX6UL_CLK_PWM3>; clock-names = "ipg", "per"; #pwm-cells = <2>; }; pwm4: pwm@0208c000 { compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm"; reg = <0x0208c000 0x4000>; interrupts = <GIC_SPI 86 IRQ_TYPE_LEVEL_HIGH>; clocks = <&clks IMX6UL_CLK_DUMMY>, <&clks IMX6UL_CLK_DUMMY>; clock-names = "ipg", "per"; #pwm-cells = <2>; };
比如上述设备树的头文件中有描述了中断控制器、时钟、PWM、DMA等各种内部外设的基本信息。
-
2、设备树的设备节点
设备树是采用设备节点的方式来进行板级信息的描述,一个外设可以通过若干个设备节点进行描述。 -
2.1、设备节点的命名
设备节点的命名有两种方式,第一种是node-name@unit-address
,节点名字@设备地址,比如一个描述PWM设备的节点可以命名为PWM0@20000000
,意思是设备PWM0的地址为0x20000000。第二种方式为label: node-name@unit-address
,标签名:节点名字@设备地址,这种方式跟第一种相比,在节点名字前面多了一个标签,并用冒号隔开。使用第二种命方式的好处是可以通过标签名直接访问节点并修改节点属性。使用第二种方式描述一个节点可以是pwm0:PWM0@20000000
。无论使用哪种方式命名,设备节点都应该简单易懂,让使用者能通过设备节点知道该设备的具体功能。 -
2.2、设备节点的属性
一个设备由很多属性,将这些属性组合起来就可以描述一个具体的设备。
-
2.2.1、根节点
根节点是整个设备树的起始,一个设备有且只有一个根节点,根节点的表示形式为/
。
imx6ull.dtsi文件
imx6ull-14x14-evk.dts
上面分别是一个dtsi和dtc的代码截屏,可以看到imx6ull-14x14-evk.dts包含了imx6ull.dtsi。这时就会出现了一个问题,两个文件里面都有根节点,在头文件被展开后,imx6ull-14x14-evk.dts中就会出现两个根节点,这难道不会编译报错吗。其实并不会报错,因为两个根节点在编译的时候,会被合并到一起。 -
2.2.2 、compatible属性
compatible也叫兼容属性,这是一个非常重要的属性,设备与驱动的匹配、系统的启动都跟这个属性相关。 -
根节点的compatible属性
在没有设备树之前,内核的启动是要通过uboot传递的一个叫machine id的变量给内核,内核将这个machine id进行匹配,如果匹配成功就启动,否则就不启动
MACHINE_START(SMDKV210, "SMDKV210") /* Maintainer: Kukjin Kim <kgene.kim@samsung.com> */ .phys_io = S3C_PA_UART & 0xfff00000, .io_pg_offst = (((u32)S3C_VA_UART) >> 18) & 0xfffc, .boot_params = S5P_PA_SDRAM + 0x100, .init_irq = s5pv210_init_irq, .map_io = smdkv210_map_io, .init_machine = smdkv210_machine_init, #ifdef CONFIG_S5P_HIGH_RES_TIMERS .timer = &s5p_systimer, #else .timer = &s3c24xx_timer, #endif MACHINE_END #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结构体中的nr成员变量为内核的机器代码,而在上面的代码中,nr为
MACH_TYPE_SMDKV210
,在mach-types.h文件中可以找到MACH_TYPE_SMDKV210宏定义对应的机器码为2456.
在使用设备树的情况下,机器码被compatible属性代替static const char *imx6ul_dt_compat[] __initconst = { "fsl,imx6ul", "fsl,imx6ull", NULL, }; DT_MACHINE_START(IMX6UL, "Freescale i.MX6 Ultralite (Device Tree)") .map_io = imx6ul_map_io, .init_irq = imx6ul_init_irq, .init_machine = imx6ul_init_machine, .init_late = imx6ul_init_late, .dt_compat = imx6ul_dt_compat, MACHINE_END
上面的代码是imx6ull的设备树的设备结构体,可以看到有一个叫dt_compat的成员变量,这个成员变量被赋值为imx6ul_dt_compat,而imx6ul_dt_compat里面就有两个属性,一个是"fsl,imx6ul",另外一个是"fsl,imx6ull"。这两个属性正好跟前面讲的imx6ull-14x14-evk.dts文件中的compatible属性相匹配。
- 设备节点compatible属性
设备节点的compatible属性主要用来跟驱动进行匹配。当设备的compatible与驱动的compatible的名字相匹配的时候,驱动程序就会被执行,从而从设备树中获取相应的设备属性。
设备端compatible
mytestled: atkled{ compatible = "fsl,imx6ull atkled"; #address-cells = <1>; #size-cells = <1>; reg = < 0x20E02F4 0x04 // SW_PAD_CTL_PAD_GPIO1_IO03 0x20E0068 0x04 // SW_MUX_CTL_PAD_GPIO1_IO03 0x209C000 0x4000 // GPIO1 >; status = "okay"; };
驱动端
static const struct of_device_id led_of_match[] = { {.compatible = "fsl,imx6ull atkled",}, {}, }; MODULE_DEVICE_TABLE(of, led_of_match); static struct platform_driver led_driver = { .probe = led_probe, .remove = led_remove, .driver = { .name = "led_dtb", .of_match_table = led_of_match, }, };
设备端的compatible和驱动端的compatible属性相同,当驱动被加载进内核时,相关的probe函数就会被执行。
- 2.2.3 、model属性
model属性可以用来描述设备模块的信息,通常为字符串。
2.2.4 status属性
status属性用来描述设备的状态,其状态可以是下列的值
当设备状态为"okay"时,设备表示可用,驱动可以正常获取设备信息。
当设备状态为"disable"时,设备表示不可用,驱动不能正常获取设备信息。- 2.2.5 、#address-cells 和#size-cells 属性
在32位系统中,#address-cells 和#size-cells 属性通常为4字节32位,#address-cells 和#size-cells 属性可以用在任何的子设备节点中。#address-cells描述了子节点中reg属性的地址信息,#size-cells描述了子节点中reg属性的长度信息。
spi4 { compatible = "spi-gpio"; #address-cells = <1>; #size-cells = <0>; gpio_spi: gpio_spi@0 { compatible = "fairchild,74hc595"; reg = <0>; }; }; aips3: aips-bus@02200000 { compatible = "fsl,aips-bus", "simple-bus"; #address-cells = <1>; #size-cells = <1>; dcp: dcp@02280000 { compatible = "fsl,imx6sl-dcp"; reg = <0x02280000 0x4000>; }; };
在上面代码中,spi4节点的#address-cells为1,#size-cells为0,代表子节点的reg属性地址信息为1个32位的字节,子节点的reg属性长度为0。所以在第8行中spi4的子节点reg属性为
reg = <0>
,通常表示spi设备的地址。
而aips3节点的#address-cells为1,#size-cells为1,代表子节点的reg属性地址信息为1个32位的字节,子节点的reg属性长度为1。所以在第19行中aips3的子节点reg属性为reg = <0x02280000 0x4000>
,代表dcp设备的起始地址为0x02280000 ,长度为0x4000。
通常#address-cells和#size-cells两个属性组合起来可以代表设备的起始地址和长度。- 2.2.6、reg属性
reg属性一般用来描述设备的地址空间资源情况或者设备的地址信息。
gpio4: gpio@020a8000 { compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio"; reg = <0x020a8000 0x4000>; interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 73 IRQ_TYPE_LEVEL_HIGH>; gpio-controller; #gpio-cells = <2>; interrupt-controller; #interrupt-cells = <2>; };
比如gpio4这个节点的reg属性为
reg = <0x020a8000 0x4000>
,就代表gpio4这个设备的起始地址为0x020a8000,地址长度为0x4000。&i2c2 { clock_frequency = <100000>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_i2c2>; status = "okay"; ft5426:ft5426@38{ compatible = "edt,edt-ft5426"; reg = <0x38>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_tsc &pinctrl_tsc_reset >; interrupt-parent = <&gpio1>; interrupts = <9 0>; rst_gpio = <&gpio5 9 GPIO_ACTIVE_LOW>; interrupt_gpio = <&gpio1 9 GPIO_ACTIVE_LOW>; }; }
比如在i2c2中有一个子节点名字为ft5426,其reg属性为
reg = <0x38>
,代表ft5426的i2c地址为0x38。- 3、修改设备节点
修改设备树节点有两种方法,一种是直接修改,一种是通过标签进行修改
直接修改
i2c1: i2c@021a0000 { #address-cells = <1>; #size-cells = <0>; compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c"; reg = <0x021a0000 0x4000>; interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>; clocks = <&clks IMX6UL_CLK_I2C1>; status = "okay"; // 添加子节点 at24c04:at24c04@038{ compatible = "fsl,at24c04"; reg = <0x38>; status = "okay"; }; };
通过标号修改
i2c1: i2c@021a0000 { #address-cells = <1>; #size-cells = <0>; compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c"; reg = <0x021a0000 0x4000>; interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>; clocks = <&clks IMX6UL_CLK_I2C1>; status = "okay"; }; &i2c1{ // 添加子节点 at24c04:at24c04@038{ compatible = "fsl,at24c04"; reg = <0x38>; status = "okay"; }; };
- 1、头文件包含
-
Linux 设备树
2022-01-20 10:56:09要想了解为什么会有设备树, 设备树是怎么来的, 我们就要先来回顾一下在没有设备树之前我们是怎么来写一个驱动程序的。 以字符设备驱动代码框架为例, 我们一起来回顾下。设备树的由来
要想了解为什么会有设备树, 设备树是怎么来的, 我们就要先来回顾一下在没有设备树之前我们是怎么来写一个驱动程序的。 以字符设备驱动代码框架为例, 我们一起来回顾下。
任何的设备驱动的编写, Linux 已经为我们打好了框架, 我们只要像做完形填空一样填写进去就可以了。字符设备驱动框架如下图所示:杂项设备驱动框架:
通过这些框架, 我们可以很容易编写驱动代码, 但是, 当我们用这个框架非常熟练的时候, 我们就会发现虽然这个方法很简单, 但是非常不容易扩展, 当我们有很多很多相似设备的时候, 如果我们都是按照这个框架来完成, 那就要写很多遍这个流程, 但是多个相似设备之间真正有差异的地方只有框架的初始化硬件的部分, 其他步骤的代码基本都是一样的。 这样就会造成大量的重复代码。 但是, 我们在编写驱动代码的时候, 我们要尽量做到代码的复用, 也就是一套驱动尽量可以兼任很多设备, 如果我们还按照这个来编写就不太符合规则了。
为了实现这个目标, 我们就要把通用的代码和有差异的代码分离出来, 来增强我们驱动代码的可移植性。 所以, 设备驱动分离的思想也就应运而生了, 在 Linux 中, 我们是在写代码的时候进行分离, 分离是把一些不相似的东西放到了 device.c, 把相似的东西放在了 driver.c, 如果我们有很多相似的设备或者平台, 我们只要修改 device.c 就可以了, 这样我们重复性的工作就大大的减少了。 这个就是平台总线的由来。
平台总线这个方法有什么弊端呢?
当我们用这个方法用习惯以后就会发现, 假如 soc 不变, 我们每换一个平台, 都要修改 C 文件, 并且还要重新编译。 而且会在 arch/arm/plat-xxx 和 arch/arm/mach-xxx 下面留下大量的关于板级细节的代码。 并不是说这个方法不好, 只是从 Linux 的发展来看, 这些代码相对于 Linux 内核来说就是“垃圾代码” , 而且这些“垃圾代码” 非常多, 于是就有了 Linux Torvalds 那句简单粗暴的话:
为了改变这个现状, 设备树也就被引进到 Linux 上了, 用来剔除相对内核来说的“垃圾代码” , 即用设备树文件来描述这些设备信息, 也就是代替 device.c 文件, platform 匹配上基本不变, 并且相比于之前的方法, 使用设备树不仅可以去掉大量“垃圾代码” , 并且采用文本格式, 方便阅读和修改, 如果需要修改部分资源, 我们也不用在重新编译内核了, 只需要把设备树源文件编译成二进制文件, 在通过 bootloader 传递给内核就可以了。 内核在对其进行解析和展开得到一个关于硬件的拓扑图。 我们通过内核提供的接口获取设备树的节点和属性就可以了。 即内核对于同一 soc 的不同主板, 只需更换设备树文件 dtb 即可实现不同主板的无差异支持, 而无需更换内核文件。
什么是设备树?
Device Tree 是一种描述硬件的数据结构, 由一系列被命名的节点(node) 和属性(property) 组成, 而节点本身可包含子节点。 所谓属性, 其实就是成对出现的 name 和 value。
在 Device Tree 中, 可描述的信息包括: CPU 的数量和类别, 内存基地址和大小, 总线和桥, 外设连接,中断控制器和中断使用情况, GPIO 控制器和 GPIO 使用情况, Clock 控制器和 Clock 使用情况。 设备树基本上就是画一棵电路板上由 CPU、 总线、 设备组成的树, Bootloader 会将这棵树传递给内核, 然后内核可以识别这棵树, 并根据它展开出 Linux 内核中的 platform_device、 i2c_client、 spi_device 等设备, 而这些设备用到的内存、 IRQ 等资源, 也被传递给了内核, 内核会将这些资源绑定给展开的相应的设备。DTS 、 DTC 和 DTB
文件.dts 是一种 ASCII 文件格式设备树描述, 在 Linux 中, 一个.dts 文件对应一个 ARM 设备, 一般放置在 arch/arm/boot/dts 目录下。
dtb 文件是 dts 文件被编译后生成的二进制文件, 由 Linux 内核解析, 有了设备树文件就可以在不改动Linux 内核的情况下, 对不同的平台实现无差异的支持, 只需更换相应的 dts 文件, 即可满足。dtc 是将 dts 编译为 dtb 的工具。 在 Linux 内核下可以单独编译设备树文件, 那么如何确定去编译我们自己的板子对应的 dts 文件? 以 i.MX6ULL 终结者为例, 我们来看一下arch/arm/boot/dts/Makefile 这个文件的内容:381 dtb-$(CONFIG_SOC_IMX6UL) += \ 382 imx6ul-14x14-ddr3-arm2.dtb \ 383 imx6ul-14x14-ddr3-arm2-emmc.dtb \ 384 imx6ul-14x14-ddr3-arm2-flexcan2.dtb \ ....... 400 dtb-$(CONFIG_SOC_IMX6ULL) += \ 401 imx6ull-14x14-ddr3-arm2.dtb \ 402 imx6ull-14x14-ddr3-arm2-adc.dtb \ 403 imx6ull-14x14-ddr3-arm2-cs42888.dtb \ 404 imx6ull-14x14-ddr3-arm2-ecspi.dtb \ 405 imx6ull-14x14-ddr3-arm2-emmc.dtb \ 406 imx6ull-14x14-ddr3-arm2-epdc.dtb \ 407 imx6ull-14x14-ddr3-arm2-flexcan2.dtb \ 408 imx6ull-14x14-ddr3-arm2-gpmi-weim.dtb \ 409 imx6ull-14x14-ddr3-arm2-lcdif.dtb \ 410 imx6ull-14x14-ddr3-arm2-ldo.dtb \ 411 imx6ull-14x14-ddr3-arm2-qspi.dtb \ 412 imx6ull-14x14-ddr3-arm2-qspi-all.dtb \ 413 imx6ull-14x14-ddr3-arm2-tsc.dtb \ 414 imx6ull-14x14-ddr3-arm2-uart2.dtb \ 415 imx6ull-14x14-ddr3-arm2-usb.dtb \ 416 imx6ull-14x14-ddr3-arm2-wm8958.dtb \ 417 imx6ull-14x14-evk.dtb \ 418 imx6ull-14x14-evk-btwifi.dtb \ 419 imx6ull-14x14-evk-emmc.dtb \ 420 imx6ull-14x14-evk-gpmi-weim.dtb \ 421 imx6ull-14x14-emmc-10.1-1280x800-c.dtb \ 422 imx6ull-14x14-emmc-7-1024x600-c.dtb \ 423 imx6ull-14x14-emmc-7-800x480-c.dtb \ 424 imx6ull-14x14-emmc-4.3-800x480-c.dtb \ 425 imx6ull-14x14-emmc-4.3-480x272-c.dtb \ 426 imx6ull-14x14-emmc-vga.dtb \ 427 imx6ull-14x14-emmc-hdmi.dtb \ 428 imx6ull-14x14-nand-10.1-1280x800-c.dtb \ 429 imx6ull-14x14-nand-7-1024x600-c.dtb \ 430 imx6ull-14x14-nand-7-800x480-c.dtb \ 431 imx6ull-14x14-nand-4.3-800x480-c.dtb \ 432 imx6ull-14x14-nand-4.3-480x272-c.dtb \ 433 imx6ull-14x14-nand-vga.dtb \ 434 imx6ull-14x14-nand-hdmi.dtb \ 435 imx6ull-14x14-evk-usb-certi.dtb \ 436 topeet_emmc_4_3.dtb \ 437 imx6ull-9x9-evk.dtb \ 438 imx6ull-9x9-evk-btwifi.dtb \ 439 imx6ull-9x9-evk-ldo.dtb 440 dtb-$(CONFIG_SOC_IMX6SLL) += \ 441 imx6sll-lpddr2-arm2.dtb \ 442 imx6sll-lpddr3-arm2.dtb \ ......
可以看出, 当选中 I.MX6ULL 这个 SOC 以后(CONFIG_SOC_IMX6ULL=y), 所有使用到 I.MX6ULL 这个 SOC的板子对应的.dts 文件都会被编译为.dtb。 如果我们使用 I.MX6ULL 新做了一个板子, 只需要新建一个此板子对应的.dts 文件, 然后将对应的.dtb 文件名添加到 dtb-$(CONFIG_SOC_IMX6ULL)下, 这样在编译设备树的时候就会将对应的.dts 编译为二进制的.dtb 文件。 第 436 行就是在 i.MX6ULL 终结者开发板移植时添加的设备树文件。
其中, DTS, DTSI, DTB, DTC, 他们之间的关系如下:DTS 设备树语法结构
一般情况下, 我们不会从头编写一个完整的 dts 文件, SOC 厂商一般会直接提供一个有着基本框架的dts 文件, 当需要添加自己的板子设备树文件时, 基于厂商提供的 dts 文件修改即可。 所以我们要了解 dts设备树文件的语法, 这样我们才清楚如何添加我们自己的设备。dtsi 头文件
由于一个 SOC 可能对应多个 ARM 设备, 这些 dts 文件势必包含许多共同的部分, Linux 内核为了简化, 把 SOC 公用的部分或者多个设备共同的部分提炼为.dtsi 文件, 类似于 C 语言的头文件。device tree source include(dtsi)是更通用的设备树代码, 也就是相同芯片但不同平台都可以使用的代码。.dtsi 文件也可以包含其他的.dtsi。 在 topeet_emmc_4_3.dts 文件中有如下内容:#include <dt-bindings/input/input.h> #include "imx6ull.dtsi"
用“#include”关键字来引用了 input.h 和 imx6ull.dtsi 文件,
在 imx6ull-14x14-evk-gpmi-weim.dts 文件中有如下内容:#include "imx6ull-14x14-evk.dts"
用“ #include” 关键字来引用了 imx6ull-14x14-evk.dts 文件, 由此可以看出在.dts 文件中可以
通过“ #include” 来引用.h、 .dtsi 和.dts 文件。
一般.dtsi 文件用于描述 SOC 的内部外设信息, 比如 CPU 架构、 主频、 外设寄存器地址范围, 比如UART、 IIC 等等。 比如 imx6ull.dtsi 就是描述 I.MX6ULL 这个 SOC 内部外设情况信息的, 内容如下:10#include <dt-bindings/clock/imx6ul-clock.h> 11#include <dt-bindings/gpio/gpio.h> 12#include <dt-bindings/interrupt-controller/arm-gic.h> 13#include "imx6ull-pinfunc.h" 14#include "imx6ull-pinfunc-snvs.h" 15#include "skeleton.dtsi" 16 17 / { 18 aliases { 19 can0 = &flexcan1; ...... 48 }; 49 50 cpus { 51 #address-cells = <1>; 52 #size-cells = <0>; 53 54 cpu0: cpu@0 { 55 compatible = "arm,cortex-a7"; 56 device_type = "cpu"; ...... 89 }; 90 }; 91 92 intc: interrupt-controller@00a01000 { 93 compatible = "arm,cortex-a7-gic"; 94 #interrupt-cells = <3>; 95 interrupt-controller; 96 reg = <0x00a01000 0x1000>, 97 <0x00a02000 0x100>; 98 }; 99 100 clocks { 101 #address-cells = <1>; 102 #size-cells = <0>; 103 104 ckil: clock@0 { 105 compatible = "fixed-clock"; 106 reg = <0>; 107 #clock-cells = <0>; 108 clock-frequency = <32768>; 109 clock-output-names = "ckil"; 110 }; ...... 135 }; 136 137 soc { 138 #address-cells = <1>; 139 #size-cells = <1>; 140 compatible = "simple-bus"; 141 interrupt-parent = <&gpc>; 142 ranges; 143 144 busfreq { 145 compatible = "fsl,imx_busfreq"; ...... 162 }; 197 198 gpmi: gpmi-nand@01806000{ 199 compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpmi-nand"; 200 #address-cells = <1>; 201 #size-cells = <1>; 202 reg = <0x01806000 0x2000>, <0x01808000 0x4000>; ...... 216 }; ...... 1177 }; 1178 };
第 54~89 行就是 cpu0 这个设备节点信息, 这个节点信息描述了 I.MX6ULL 这颗 SOC 所使用的 CPU信息, 比如架构是 cortex-A7, 频率支持 996MHz、 792MHz、 528MHz、 396MHz 和 198MHz 等等。
在 imx6ull.dtsi 文件中不仅仅描述了 cpu0 这一个节点信息, I.MX6ULL 这颗 SOC 所有的外设都描述的清清楚楚, 比如 ecspi1~4、 uart1~8、 usbphy1~2、 i2c1~4 等等。 下面我们就来介绍一下设备节点的具体信息。设备节点信息
设备树从根节点开始, 每个设备都是一个节点。 根节点就相当于树根。 节点和节点之间可以互相嵌套,形成父子关系。 可以理解为树枝可以分成好几个小的树枝。 设备的属性用 key-value 对(键值对)来描述, 每个属性用分号结束。 下面先来看一个设备树结构模板:1 / { 2 node1 { 3 a-string-property = "A string"; 4 a-string-list-property = "first string", "second string"; 5 a-byte-data-property = [0x01 0x23 0x34 0x56]; 6 child-node1 { 7 first-child-property; 8 second-child-property = <1>; 9 a-string-property = "Hello, world"; 10 }; 11 child-node2 { 12 }; 13 }; 14 node2 { 15 an-empty-property; 16 a-cell-property = <1 2 3 4>; 17 child-node1 { 18 }; 19 }; 20 }
上面的 dts 文件内容并没有实际的用途, 只是基本表示了一个设备树源文件的结构。 但是这里面体现了一些属性:
一个单独的根节点: “/”
两个子节点: “node1” 和“node2”
两个 node1 的子节点: “child-node1” 和“child-node2”
一些分散在树里的属性, 属性是最简单的键-值对, 它的值可以为空或者包含一个任意的字节流。虽然数据类型并没有编码进数据结构, 但是设备树源文件中仍有几个基本的数据表示形式:
1) 文本字符串(无结束符) , 可以用双引号表示:
a-string-property = "A string";
2) “cells” 是 32 位无符号整数, 用尖括号限定:
cell-property = <0xbeef 123 0xabcd1234>;
3) 二进制数据用方括号限定:
binary-property = [0x01 0x23 0x45 0x67];
4) 不同表示形式的数据可以用逗号连在一起:
mixed-property = "a string", [0x01 0x23 0x45 0x67], <0x12345678>;
5) 逗号也可以用于创建字符串列表:
string-list = "red fish", "blue fish";
下面我们看一下简化之后的 imx6ull.dtsi 文件中结构:1 / { 2 aliases { 3 can0 = &flexcan1; 4 }; 5 6 cpus { 7 #address-cells = <1>; 8 #size-cells = <0>; 9 10 cpu0: cpu@0 { 11 compatible = "arm,cortex-a7"; 12 device_type = "cpu"; 13 reg = <0>; 14 }; 15 }; 16 17 intc: interrupt-controller@00a01000 { 18 compatible = "arm,cortex-a7-gic"; 19 #interrupt-cells = <3>; 20 interrupt-controller; 21 reg = <0x00a01000 0x1000>, 22 <0x00a02000 0x100>; 23 }; 24 }
第 1 行, “/” 是根节点。
第 2、 6 和 17 行, aliases、 cpus 和 intc 是三个子节点。
第 10 行, cpu0 是 cpus 的子节点。
简单来说, 节点就好比一颗大树, 从树的主干开始, 然后有一节一节的树枝, 这个就叫节点。 在代码中的节点是什么样子的呢。 我们把上面模板中的根节点摘出来, 如下图所示, 这个就是根节点, 相当于大树的树干。/{ };
而树枝就相当于设备树的子节点, 同样我们把子节点摘出来就是根节点里面的 node1 和 node2, 如下图所示:
/{ //根节点 node1//子节点 node1 { }; node2//子节点 node2 { }; };
一个树枝是不是也可以继续分成好几个树枝呢, 也就是说子节点里面可以包含子子节点。 所以
child-node1 和 child-node2 是 node1 的子节点, 如下图所示:/{ //根节点 node1//子节点 node1 { child-node1 //子子节点 { }; }; node2//子节点 node2 { child-node2 //子子节点 { }; }; };
设备节点及 lable 命名
在前面的代码中, 我们注意到节点和子节点之间的命名有所不同, 它们都遵循了下面的命名格式:格式: <名称>[@<设备地址>]
<名称>节点的名称也不是任意起的, 一般要体现设备的类型而不是特点的型号, 比如网口, 应该命名为ethernet, 而不是随意起一个, 比如 111。
<设备地址>就是用来访问该设备的基地址。 但并不是说在操作过程中来描述一个地址, 他主要用来区分用。
注意事项:
同一级的节点只要地址不一样, 名字是可以不唯一的。
设备地址是一个可选选项, 可以不写。 但为了容易区分和理解, 一般是都写的。
当我们找一个节点的时候, 我们必须书写完整的节点路径, 如果我们的节点名很长, 那么我们在引用的时候就十分不方便, 所以, 设备树允许我们用下面的形式为节点标注引用(起别名)。 比如一个动漫人物的名字是蒙其· D· 路飞, 他的小名是路飞, 那是不是小名要比我们的全名更容易记忆了。 这个就是别名。举例:uart8: serial@02288000
其中, uart8 就是这个节点名称的别名, serial@02288000 就是节点名称。
一般我往一个节点里面添加内容的时候, 不会直接把添加的内容写到节点里面, 而是通过节点的引用来添加。举例&uart8 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_uart8>; status = "okay"; };
&uart8 表示引用节点别名为 uart8 的节点, 并往这个节点里面添加以下内容:
pinctrl-names = "default"; pinctrl-0 = <&pinctrl_uart8>; status = "okay";
注意事项:编译设备树的时候, 相同的节点的不同属性信息都会被合并, 相同节点的相同的属性会被重写, 使用引用可以避免四处找节点。 如 dts 和 dtsi 里面都有根节点, 但最终会合并成一个根节点。
标准属性
address-cells 和 size-cells 属性
不同的平台, 不同的总线, 地址位长度可能不同, 有 32 位地址, 有 64 位地址, 为了适应这个, 规范规定一个 32 位的长度为一个 cell。
"#address-cells"属性用来表示总线地址需要几个 cell 表示, 该属性本身是 u32 类型的。
"#size-cells"属性用来表示子总线地址空间的长度需要几个 cell 表示, 属性本身的类型也是 u32。
可以这么理解父节点表示总线, 总线上每个设备的地址长度以及地址范围是总线的一个特性, 用
"#address-cells","#size-cells"属性表示, 比如总线是 32 位, 那么"#address-cells"设置成 1 就可以了。 这两个属性不可以继承, 就是说在未定义这两个属性的时候, 不会继承更高一级父节点的设置, 如果没有设置的话, 内核默认认为"#address-cells"为 2, "#size-cells"为 1。 举例来说, 如下所示:1 spi4 { 2 compatible = "spi-gpio"; 3 #address-cells = <1>; 4 #size-cells = <0>; 5 6 gpio_spi: gpio_spi@0 { 7 compatible = "fairchild,74hc595"; 8 reg = <0>; 9 }; 10 }; 11 12 aips3: aips-bus@02200000 { 13 compatible = "fsl,aips-bus", "simple-bus"; 14 #address-cells = <1>; 15 #size-cells = <1>; 16 17 dcp: dcp@02280000 { 18 compatible = "fsl,imx6sl-dcp"; 19 reg = <0x02280000 0x4000>; 20 }; 21 };
第 3, 4 行, 节点 spi4 的#address-cells = <1>, #size-cells = <0>, 说明 spi4 的子节点 reg 属性中起始地址所占用的字长为 1, 地址长度所占用的字长为 0。 因此第 8 行 reg 属性值为 <0>, 相当于设置了起始地址, 而没有设置地址长度。
第 14, 15 行, 设置 aips3: aips-bus@02200000 节点#address-cells = <1>, #size-cells = <1>, 说明 aips3:aips-bus@02200000 节点起始地址长度所占用的字长为 1, 地址长度所占用的字长也为 1。 因此第 19 行,子节点 dcp: dcp@02280000 的 reg 属性值为<0x02280000 0x4000>, 相当于设置了起始地址为 0x02280000,地址长度为 0x40000。reg 属性
"reg"属性用来表示节点地址资源的, 比如常见的就是寄存器的起始地址及大小。 要想表示一块连续地址, 必须包含起始地址和空间大小两个参数, 如果有多块地址, 那么就需要多组这样的值表示。 对于'reg'属性, 每个元素是一个二元组, 包含起始地址和大小。 还有另外一个问题, 地址和大小用几个 u32 表示呢?这个就由父节点的"#address-cells","#size-cells"属性确定。
比如在 imx6ull.dtsi 中有如下内容:323 uart1: serial@02020000 { 324 compatible = "fsl,imx6ul-uart", 325 "fsl,imx6q-uart", "fsl,imx21-uart"; 326 reg = <0x02020000 0x4000>; 327 interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>; 328 clocks = <&clks IMX6UL_CLK_UART1_IPG>, 329 <&clks IMX6UL_CLK_UART1_SERIAL>; 330 clock-names = "ipg", "per"; 331 status = "disabled"; 332 };
上述代码是节点 uart1, uart1 节点描述了 I.MX6ULL 的 UART1 相关信息, 重点是第 326 行的 reg 属性。 其中 uart1 的父节点 aips1: aips-bus@02000000 设置了#address-cells = <1>、 #size-cells = <1>, 因此 reg属性中 address=0x02020000, length=0x4000。
compatible 属性
设备树中的每个表示一个设备的节点都需要一个 compatible 属性, compatible 属性是操作系统用来决定设备和驱动绑定的关键因素。 compatible 属性也叫做兼容性属性, 属性的值是一个字符串列表, 用于表示是何种设备, 可以在代码中进行匹配。举例:compatible = "manufacturer,model";
第一个字符串表示厂商, 后面的字符串表示确切的设备名称。 比如在 topeet_emmc_4_3.dts 文件中sound 节点表示开发板的音频设备节点, i.MX6ULL 终结者开发板上的音频芯片是欧胜(WOLFSON)出品的WM8960, sound 节点的 compatible 属性值如下:
compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960";
属性值有两个, 分别为“fsl,imx6ul-evk-wm8960” 和“fsl,imx-audio-wm8960” , 其中“fsl” 表示厂商是飞思卡尔, “imx6ul-evk-wm8960” 和“imx-audio-wm8960” 表示设备驱动的名字。 sound 这个设备首先使用第一个兼容值在 Linux 内核里面查找, 看看能不能找到与之匹配的驱动文件, 如果没有找到的话就使用第二个兼容值查找, 直到找到或者查找完整个 Linux 内核也没有找到对应的驱动。
status 属性
status 属性用来表示节点的状态, 其实就是硬件的状态, 用字符串表示。
“okay” 表示硬件正常工作
“disable” 表示当前硬件不可用
“fail” 表示因为出错不可用
“fail-sss” 表示某种原因出错不可用, sss 表示具体出错的原因。
实际中, 基本只用“okay” 和“disabl” 。
-
linux 设备树详解
2022-04-19 13:44:47设备树原理,介绍设备树构建过程。2 Linux 设备树
2.1 什么是设备树?
设备树(Device Tree),将这个词分开就是“设备”和“树”,描述设备树的文件叫做DTS(Device Tree Source),这个 DTS 文件采用树形结构描述板级设备,也就是开发板上的设备信息。
设备树的机制其实也是总线型的 BUS/Dev/Drv 模型,只是编写 Dev 的方式变了。即编写 设备树文件 .dts。dst 文件会被编译成 dtb 文件。dtb文件会传给内核, 内核会解析dtb文件, 构造出一系列的 device_node 结构体,
device_node 结构体会转换为 platform_device 结构体。不了解 总线型驱动可看另外一篇:
https://blog.csdn.net/weixin_46640184/article/details/124229608
对应的实例可以看这篇
https://blog.csdn.net/weixin_46640184/article/details/124291470所以: 我们可以在 dts 文件中指定资源, 不再需要在 .c 文件中设置 platform_device 结构体
“来自dts的platform_device结构体” 与 “我们写的platform_driver” 的匹配过程:
- "来自 dts 的 platform_device 结构体"里面有成员 “.dev.of_node”, 它里面含有各种属性, 比如 compatible, reg, pin
- “我们写的 platform_driver” 里面有成员 “.driver.of_match_table”, 它表示能支持哪些来自于 dts 的platform_device
如果 “of_node 中的 compatible” 跟 “of_match_table 中的 compatible” 一致, 就表示匹配成功, 则调用 platform_driver中的probe函数; 在probe函数中, 可以继续从of_node中获得各种属性来确定硬件资源。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9zKh4329-1650347058800)(assets/image-20220419103127867.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cDajUKjD-1650347058801)(assets/image-20220419103411940.png)]
2.2 设备树的规范
2.2.1 DTS 格式
//语法: //Devicetree node格式: [label:] node-name[@unit-address] { [properties definitions] [child nodes] }; //Property格式1: [label:] property-name = value; //Property格式2(没有值): [label:] property-name; /* Property取值只有3种: * arrays of cells(1个或多个32位数据, 64位数据使用2个32位数据表示), * string(字符串), * bytestring(1个或多个字节) */ //示例: //a. Arrays of cells : cell就是一个32位的数据 interrupts = <17 0xc>; //b. 64bit数据使用2个cell来表示: clock-frequency = <0x00000001 0x00000000>; //c. A null-terminated string (有结束符的字符串): compatible = "simple-bus"; //d. A bytestring(字节序列) : local-mac-address = [00 00 12 34 56 78]; // 每个byte使用2个16进制数来表示, 必须两位 local-mac-address = [000012345678]; // 每个byte使用2个16进制数来表示 // 可以是各种值的组合, 用逗号隔开: compatible = "ns16550", "ns8250"; example = <0xf00f0000 19>, "a strange property format";
2.2.2 DTS 文件布局
/dts-v1/; //版本 [memory reservations] // 格式为: /memreserve/ <address> <length>; / { // '/'表示根节点 [property definitions] [child nodes] };
[memory reservations] 作用:
留出该空间给自己使用。
2.2.3 特殊的、默认的属性
-
根节点
-
#address-cells
在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address)
-
#size-cells
在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size)
-
compatible
定义一系列的字符串, 用来指定内核中哪个machine_desc可以支持本设备
即这个板子兼容哪些平台
uImage : smdk2410 smdk2440 mini2440 ==> machine_desc -
model
咱这个板子是什么
比如有2款板子配置基本一致, 它们的 compatible 是一样的
那么就通过model来分辨这2款板子
-
-
/memory
device_type = "memory"; reg = < start_addr memory_size >; // 用来指定内存的地址、大小 //e.g.: memory{ device_type = "memory"; reg = <0x80000000 0x20000000>; }
-
/chosen
bootargs // 内核command line参数, 跟u-boot中设置的bootargs作用一样 //e.g.: chosen { bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200"; };
-
/cpus
/cpus 节点下有 1 个或多个 cpu 子节点, cpu 子节点中用 reg 属性用来标明自己是哪一个 cpu
所以 /cpus 中有以下2个属性:-
#address-cells
在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address)
-
#size-cells
在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size)
必须设置为0
cpus { cpu { compatible = "arm,arm926ej-s"; }; };
-
2.2.4 引用其他节点:
phandle
节点中的phandle属性, 它的取值必须是唯一的(不要跟其他的phandle值一样)
pic@10000000 { phandle = <1>; interrupt-controller; }; another-device-node { interrupt-parent = <1>; // 使用phandle值为1来引用上述节点 };
label
PIC: pic@10000000 { interrupt-controller; }; another-device-node { interrupt-parent = <&PIC>; // 使用label来引用上述节点, // 使用lable时实际上也是使用phandle来引用, // 在编译dts文件为dtb文件时, 编译器dtc会在dtb中插入phandle属性 };
2.2.5 DTB 文件布局
2.3 内核对设备树的处理
2.3.1 前提
Linux uses DT data for three major purposes:
- platform identification,
- runtime configuration, and
- device population.
bootloader启动内核时,会设置r0,r1,r2三个寄存器,
- r0一般设置为0;
- r1一般设置为machine id (在使用设备树时该参数没有被使用);
- r2一般设置ATAGS或DTB的开始地址
2.3.2 设备树中平台信息的处理
-
设备树根节点的compatible属性列出了一系列的字符串,
表示它兼容的单板名,
从"最兼容"到次之
-
内核中有多个machine_desc, 其中有dt_compat成员, 它指向一个字符串数组, 里面表示该 machine_desc支持哪些单板,
-
使用 compatile 属性的值, 跟每一个 machine_desc.dt_compat 比较,成绩为"吻合的 compatile 属性值的位置", 成绩越低越匹配, 对应的 machine_desc 即被选中,选中后就采用该 machine_desc 的初始化函数初始化。
2.3.3 函数调用过程
start_kernel // init/main.c setup_arch(&command_line); // arch/arm/kernel/setup.c mdesc = setup_machine_fdt(__atags_pointer); // arch/arm/kernel/devtree.c early_init_dt_verify(phys_to_virt(dt_phys) // 判断是否有效的dtb, drivers/of/ftd.c initial_boot_params = params; mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach); // 找到最匹配的machine_desc, drivers/of/ftd.c while ((data = get_next_compat(&compat))) { score = of_flat_dt_match(dt_root, compat); if (score > 0 && score < best_score) { best_data = data; best_score = score; } } machine_desc = mdesc;
2.4 对设备树中运行时配置信息的处理
2.4.1 函数调用过程
start_kernel // init/main.c setup_arch(&command_line); // arch/arm/kernel/setup.c mdesc = setup_machine_fdt(__atags_pointer); // arch/arm/kernel/devtree.c early_init_dt_scan_nodes(); // drivers/of/ftd.c /* Retrieve various information from the /chosen node */ of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line); /* Initialize {size,address}-cells info */ of_scan_flat_dt(early_init_dt_scan_root, NULL); /* Setup memory, calling early_init_dt_add_memory_arch */ of_scan_flat_dt(early_init_dt_scan_memory, NULL);
- /chosen节点中bootargs属性的值, 存入全局变量: boot_command_line
- 确定根节点的这2个属性的值: #address-cells, #size-cells
存入全局变量: dt_root_addr_cells, dt_root_size_cells - 解析/memory中的reg属性, 提取出"base, size", 最终调用memblock_add(base, size);
2. 5 dtb 转换为device_node(unflatten)
2.5.1 函数调用过程
start_kernel // init/main.c setup_arch(&command_line); // arch/arm/kernel/setup.c arm_memblock_init(mdesc); // arch/arm/kernel/setup.c early_init_fdt_reserve_self(); /* Reserve the dtb region */ // 把DTB所占区域保留下来, 即调用: memblock_reserve early_init_dt_reserve_memory_arch(__pa(initial_boot_params), fdt_totalsize(initial_boot_params), 0); early_init_fdt_scan_reserved_mem(); // 根据dtb中的memreserve信息, 调用memblock_reserve unflatten_device_tree(); // arch/arm/kernel/setup.c __unflatten_device_tree(initial_boot_params, NULL, &of_root, early_init_dt_alloc_memory_arch, false); // drivers/of/fdt.c /* First pass, scan for size */ size = unflatten_dt_nodes(blob, NULL, dad, NULL); /* Allocate memory for the expanded device tree */ mem = dt_alloc(size + 4, __alignof__(struct device_node)); /* Second pass, do actual unflattening */ unflatten_dt_nodes(blob, mem, dad, mynodes); populate_node np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl, __alignof__(struct device_node)); np->full_name = fn = ((char *)np) + sizeof(*np); populate_properties pp = unflatten_dt_alloc(mem, sizeof(struct property), __alignof__(struct property)); pp->name = (char *)pname; pp->length = sz; pp->value = (__be32 *)val;
2.5.2 规则
-
在DTB文件中,
每一个节点都以TAG(FDT_BEGIN_NODE, 0x00000001)开始, 节点内部可以嵌套其他节点,
每一个属性都以TAG(FDT_PROP, 0x00000003)开始 -
每一个节点都转换为一个device_node结构体:
利用该结构体构造树。
struct device_node { const char *name; // 来自节点中的name属性, 如果没有该属性, 则设为"NULL" const char *type; // 来自节点中的device_type属性, 如果没有该属性, 则设为"NULL" phandle phandle; const char *full_name; // 节点的名字, node-name[@unit-address] struct fwnode_handle fwnode; struct property *properties; // 节点的属性 struct property *deadprops; /* removed properties */ struct device_node *parent; // 节点的父亲 struct device_node *child; // 节点的孩子(子节点) struct device_node *sibling; // 节点的兄弟(同级节点) #if defined(CONFIG_OF_KOBJ) struct kobject kobj; #endif unsigned long _flags; void *data; #if defined(CONFIG_SPARC) const char *path_component_name; unsigned int unique_id; struct of_irq_controller *irq_trans; #endif };
-
device_node结构体中有properties, 用来表示该节点的属性
//每一个属性对应一个property结构体: struct property { char *name; // 属性名字, 指向dtb文件中的字符串 int length; // 属性值的长度 void *value; // 属性值, 指向dtb文件中value所在位置, 数据仍以big endian存储 struct property *next; #if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC) unsigned long _flags; #endif #if defined(CONFIG_OF_PROMTREE) unsigned int unique_id; #endif #if defined(CONFIG_OF_KOBJ) struct bin_attribute attr; #endif };
-
这些device_node构成一棵树, 根节点为: of_root
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JQvHDi0U-1650347058808)(assets/image-20220419130539279.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jsDnxQWl-1650347058809)(assets/image-20220419130112484.png)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HJFJEhde-1650347058809)(assets/image-20220419130333904.png)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q21U6JYf-1650347058809)(assets/image-20220419130424248.png)]
2.6 device_node转换为platform_device
dts -> dtb -> device_node -> platform_device
两个问题:
-
哪些device_node可以转换为platform_device?
- 根节点下含有compatile属性的子节点
- 如果一个结点的compatile属性含有这些特殊的值(“simple-bus”,“simple-mfd”,“isa”,“arm,amba-bus”)之一, 那么它的子结点(需含compatile属性)也可以转换为platform_device
- i2c, spi等总线节点下的子节点, 应该交给对应的总线驱动程序来处理, 它们不应该被转换为platform_device
-
怎么转换?
- platform_device中含有resource数组, 它来自device_node的reg, interrupts属性;
- platform_device.dev.of_node指向device_node, 可以通过它获得其他属性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K6mBk2JB-1650347058810)(assets/image-20220419131303274.png)]
2.7 Dev 和 Drv 比配过程
-
平台总线上有个
其中的 platform_match 用来判断 platform_device 与 platform_drive 是否匹配
-
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jzKXZzp4-1650347058811)(assets/image-20220419132505934.png)]
e.g.:
- 先比较
platform_device.driver_override
和platform_driver.driver.name
- 再逐个比较
platform_device.dev.comatible
和platform_driver.driver.of_match_table
- 然后比较
platform_device.name
和platform_driver.idtable
- 最后比较
paltform_device.name
和platform_driver.driver.name
按这些顺序比较,有一个成功就采用。
2.8 总结
-
内核函数of_platform_default_populate_init, 遍历device_node树, 生成platform_device
-
并非所有的device_node都会转换为platform_device,只有以下的device_node会转换:
-
该节点必须含有compatible属性
-
根节点的子节点(节点必须含有compatible属性)
-
含有特殊compatible属性的节点的 子节点(子节点必须含有compatible属性):
这些特殊的compatilbe属性为: “simple-bus”, “simple-mfd”, “isa”, “arm,amba-bus”
//e.g.: /* /mytest会被转换为platform_device, 因为它兼容"simple-bus", 它的子节点/mytest/mytest@0 也会被转换为platform_device /i2c节点一般表示i2c控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver; /i2c/at24c02节点不会被转换为platform_device, 它被如何处理完全由父节点的platform_driver决定, 一般是被创建为一个i2c_client。 */ / { mytest { compatile = "mytest", "simple-bus"; mytest@0 { compatile = "mytest_0"; }; }; i2c { compatile = "samsung,i2c"; at24c02 { compatile = "at24c02"; }; }; };
-
2.9 内核中设备树的操作函数
include/linux/ 目录下有很多 of 开头的头文件:
处理 DTB
of_fdt.h
dtb文件的相关操作函数, 我们一般用不到, 因为dtb文件在内核中已经被转换为device_node树(它更易于使用)
处理device_node
of.h // 提供设备树的一般处理函数, //比如 of_property_read_u32(读取某个属性的u32值), of_get_child_count(获取某个device_node的子节点数) of_address.h // 地址相关的函数, 比如 of_get_address(获得reg属性中的addr, size值) of_match_device(从matches数组中取出与当前设备最匹配的一项) of_dma.h // 设备树中DMA相关属性的函数 of_gpio.h // GPIO相关的函数 of_graph.h // GPU相关驱动中用到的函数, 从设备树中获得GPU信息 of_iommu.h // 很少用到 of_irq.h // 中断相关的函数 of_mdio.h // MDIO (Ethernet PHY) API of_net.h // OF helpers for network devices. of_pci.h // PCI相关函数 of_pdt.h // 很少用到 of_reserved_mem.h // reserved_mem的相关函数
处理 platform_device
of_platform.h // 把device_node转换为platform_device时用到的函数, // 比如of_device_alloc(根据device_node分配设置platform_device), // of_find_device_by_node (根据device_node查找到platform_device), // of_platform_bus_probe (处理device_node及它的子节点) of_device.h // 设备相关的函数, 比如 of_match_device
2.10 在根文件系统中查看设备树
-
/sys/firmware/fdt
原始dtb文件
hexdump -C /sys/firmware/fdt
-
/sys/firmware/devicetree
以目录结构程现的dtb文件, 根节点对应base目录, 每一个节点对应一个目录, 每一个属性对应一个文件
-
/sys/devices/platform
系统中所有的platform_device, 有来自设备树的, 也有来有.c文件中注册的
对于来自设备树的platform_device, 可以进入 /sys/devices/platform/<设备名>/of_node 查看它的设备树属性
-
/proc/device-tree
是链接文件, 指向
/sys/firmware/devicetree/base
-
linux设备树
2021-10-28 13:38:28linux设备树一、什么是设备树二、linux设备树详细三、linux设备树举例 一、什么是设备树 设备树是一种描述硬件的数据结构,起源于OpenFirmware。许多硬件信息通过它传递给内核。 设备树由一系列节点(Node)和属性... -
ARM Linux设备树
2022-03-24 11:21:561、设备树 在过去的ARM Linux源码中,arch/arm/plat-...设备树的中节点会转化为linux中device,会代替平台驱动中的device和平台驱动进行匹配。 设备树文件也可以包含引用C语言的头文件 2、设备树编译 2.1 通过内核编 -
i.MX6ULL驱动开发 | 04-Linux设备树基本语法与实例解析
2022-03-16 10:23:31一、设备树简介 1. 设备树在ARM架构的引入 在之前使用S3C2440开发板移植Linux 3.4.2内核时,修改了很多关于c文件去适配开发板,和开发板相关的文件放在arch/arm/mxch-xxx目录下,因此linux内核arm架构下添加了很多... -
Linux设备树dts移植详解
2021-05-14 12:41:39作为U-Boot 和Linux 内核之间的动态接口,本文阐述了设备树的数据存储格式以及源码描述语法,进而分析了U-Boot 对扁平设备树的支持设置,Linux 内核对设备树的解析流程。关键词:扁平设备树; DTS; PowerPC; LinuxIBM... -
嵌入式Linux开发19——Linux设备树(万字总结)
2021-08-15 20:48:51提示:本文参考《 Devicetree SpecificationV0.2.pdf 》、《Power_...文章目录设备树的概念DTS、 DTB 和 DTCDTS语法1.dtsi头文件2.设备节点3.标准属性3.1 compatible 属性3.2 model属性3.3 status 属性3.4 #add -
Linux 设备树详解之设备树dts语法
2022-05-21 18:13:30Linux设备树是一个硬件配置集合。源文件后缀为dts,通过编译器dtc将dts编译为二进制文件dtb。设备树顾名思义是一个以树状结构表示资源配置。 节点命名方式,节点名字只能由数字,字母和 <,> <.> <_&... -
Linux_设备树
2022-01-01 19:05:541 设备树起源 Linux 3.x之前,arch/arm/plat-...在Linux 2.6中,ARM架构的板级硬件细节过多地被硬编码在arch/arm/plat-xxx和arch/arm/mach-xxx中,采用设备树后,许多硬件的细节可以直接通过它传递给Linux,而不需要在 -
Linux设备树中一些标准属性介绍
2020-11-20 23:25:34Linux设备树中一些标准属性介绍标准属性1、compatible 属性2、 model 属性3、 status 属性4、 #address-cells 和#size-cells 属性5、 reg 属性6、 ranges 属性7、 name 属性8、 device_type 属性 标准属性 节点是由... -
关于linux设备树的简单理解(基于linux-5.13.5)
2021-10-05 23:48:13设备树文件所在路径:linux-5.13.5\arch\arm\boot\dts 每个xxx.dts对应一个板子 dts目录下有两种文件:xxx.dts xxx.dtsi, 因为每个板子都有一个xxx.dts文件,在有多个板子共用同一个芯片时,那么dts文件中对于芯片... -
linux 设备树
2017-09-01 22:19:36linux 设备树参考地址 http://blog.csdn.net/green1900/article/details/45646095 http://www.cnblogs.com/xiaojiang1025/p/6131381.html http://blog.csdn.net/21cnbao/article/details/84575461.为什么要使用... -
linux设备树文件(dtb)
2021-05-11 20:38:38linux 自内核3.x之后引入设备树的概念.而不像2.6时代在arch/arm/mach-s2440.c添加修改设备信息。(推荐一下宋宝华的《Linux设备驱动开发详解:基于最新的Linux 4.0内核》,奉劝各位正学习嵌入式linux的在校学生也要... -
Linux 设备树(Device Tree)(转载)
2021-05-14 03:22:18ARM Device Tree起源Linus Torvalds在2011年3月17日的ARM Linux邮件列表宣称 "this whole ARM thing is a f*cking pain in the ass",引发ARM Linux社区的地震,随后ARM社区进行了一系列的重大修正。在过去的ARM ... -
Linux 设备树详解
2018-11-07 15:37:44本文基于天嵌E9V3开发板,详解设备树的语法结构和用法。 一、设备树 二、E9V3设备树总览 E9V3设备树总览: 三、设备树编写规则 -
Linux查看设备树节点的相关信息
2022-04-28 15:28:03内核升级以后,devices的信息就在设备树里面进行描述了。某些时候,我们可能需要查看当前外设对应的设备树里面配置的信息,比如分辨率、时钟等。在这里做一个记录。 比如mmc里面存在如下属性: compatible、bus-... -
Linux驱动-设备树
2022-04-01 14:44:50linux/init.h> //初始化头文件 #include <linux/module.h> //最基本的文件,支持动态添加和卸载模块。 #include <linux/of.h> //添加头文件 int size; //定义长度 u32 out_values[2] = {0}; ... -
Linux设备驱动程序学习(十六)——Linux设备树解析
2019-08-06 09:54:01设备树简介 在内核源码中,存在大量对板级细节信息描述的代码。这些代码充斥在/arch/arm/plat-xxx和/arch/arm/mach-xxx目录,对内核而言这些platform设备、resource、i2c_board_info、spi_board_info以及各种... -
Linux 设备树的加载与匹配
2021-04-08 10:08:11什么是设备树 在内核源码中存在大量对板级细节信息描述的代码,但是对于内核而言,这些代码对于内核毫无意义。 ARM内核版本3.x引入了Flattened Device Tree(FDT),这是一种描述硬件资源的数据结构,通过BootLoader将... -
linux设备树dts一之移植详解
2021-05-12 10:55:27http://blog.csdn.net/cosmoslhf/article/details/9252509摘 要:...作为U-Boot 和Linux 内核之间的动态接口,本文阐述了设备树的数据存储格式以及源码描述语法,进而分析了U-Boot 对扁平设备树的支持设置,Linux ... -
Linux驱动开发|设备树介绍
2021-10-26 22:20:19设备树介绍 -
Linux设备树的一些命令
2020-04-03 11:30:00Linux设备树的初步接触 1. 编译设备树 # 在Linux kernel的源目录下: make make ARCH=arm CROSS_COMPILE=arm-xxxxx-linux- dtbs 2. 反编译设备树 # 在Linux kernel的源目录下(在其他目录也行): scripts/dtc/dtc -I ... -
arm linux设备树
2020-10-23 16:07:24文章目录设备树概述设备树的特点设备树的使用编译设备树内核如何加载dtb 设备树概述 在linux2.6以前,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量重复的、用于描述设备细节的代码。导致内核代码迅速膨胀,... -
Linux 设备树添加spi设备
2021-05-12 17:20:34Linux:4.6应用开发板:zynq系列 zc706、zedboard文件系统:ubuntu12参考帖子:...当时,关于spi设备树的节点描述如下:spi@e00... -
【Linux驱动开发】Linux设备树详解
2019-12-05 10:49:25设备树(Device Tree),将这个词分开就是“设备”和“树”,描述设备树的文件叫做 DTS(Device Tree Source),这个 DTS 文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如 CPU 数量、 内存基地址、IIC ... -
linux 设备树(.dts)实战解析
2021-04-11 15:53:53文章目录前言设备树DTS语法内容标准属性向节点追加或修改内容设备树在目录中的体现Linux 内核解析 DTB 文件帮助文档信息设备树节点的操作函数1、查找节点的 of 函数2、查找父/子节点的 OF 函数3、提取属性值的 OF ...