1. 设备树概念
设备树常用概念有:dtc(用来编译设备树的工具)、dts(设备树描述文件)、dtsi(设备树头文件)、dtb(编译后的二进制文件)。
dtc设备树编译工具:dtc是用来编译设备树的工具,就像gcc可以用来编译C语言一样,设备树源文件也需要编译器来对它进行编译,而这个编译器就是dtc。dtc编译工具的源代码在内核根目录下的/scripts/dtc文件夹中,在对应的Makefile中可以看到内核将跟dtc有关的文件都进行编译,最后生成一个可执行程序。"make dtbs"可以对设备树文件进行编译生成设备文件后缀为xxx.dtb。
# scripts/dtc makefile
hostprogs-y := dtc
always := $(hostprogs-y)
dtc-objs := dtc.o flattree.o fstree.o data.o livetree.o treesource.o \
srcpos.o checks.o util.o
dtc-objs += dtc-lexer.lex.o dtc-parser.tab.o
# Source files need to get at the userspace version of libfdt_env.h to compile
HOSTCFLAGS_DTC := -I$(src) -I$(src)/libfdt
HOSTCFLAGS_checks.o := $(HOSTCFLAGS_DTC)
HOSTCFLAGS_data.o := $(HOSTCFLAGS_DTC)
HOSTCFLAGS_dtc.o := $(HOSTCFLAGS_DTC)
HOSTCFLAGS_flattree.o := $(HOSTCFLAGS_DTC)
HOSTCFLAGS_fstree.o := $(HOSTCFLAGS_DTC)
HOSTCFLAGS_livetree.o := $(HOSTCFLAGS_DTC)
HOSTCFLAGS_srcpos.o := $(HOSTCFLAGS_DTC)
HOSTCFLAGS_treesource.o := $(HOSTCFLAGS_DTC)
HOSTCFLAGS_util.o := $(HOSTCFLAGS_DTC)
HOSTCFLAGS_dtc-lexer.lex.o := $(HOSTCFLAGS_DTC)
HOSTCFLAGS_dtc-parser.tab.o := $(HOSTCFLAGS_DTC)
# dependencies on generated files need to be listed explicitly
$(obj)/dtc-lexer.lex.o: $(obj)/dtc-parser.tab.h
# generated files need to be cleaned explicitly
clean-files := dtc-lexer.lex.c dtc-parser.tab.c dtc-parser.tab.h
dts/dtsi设备树源文件:dts是设备树的源文件,驱动开发者需要在dts文件中构造描述板级的设备树,其位置一般位于内核根目录下的/arch/arm/boot/dts(以arm架构为例)。dts编写好之后会用dtc来进行编译,最后会生成dtb二进制文件。
dtsi设备树头文件:设备树就像C语言,同样可以进行头文件包含。开发者通常会把同一个品牌芯片的共同点提取出来,将相同的代码写成一个头文件供其他文件包含。包含dtsi文件跟C语言的头文件包含用法一样,也是include"xxx.dtsi"。
dtb设备树二进制文件:dtb是由dts/dtsi被编译后生成的二进制文件,也是最终真正的设备树,dtb文件是可以被内核解析的文件,所以dtb文件会被烧到系统中,由uboot指定dtb的地址供内核去读取。
2. 设备树的结构
以imx6dl-hummingboard.dts以及imx6dl.dtsi文件为例,来理解设备树结构。
- imx6dl-hummingboard.dts 文件节选
/dts-v1/; #include "imx6dl.dtsi" #include "imx6qdl-microsom.dtsi" #include "imx6qdl-microsom-ar8035.dtsi" / { model = "SolidRun HummingBoard DL/Solo"; compatible = "solidrun,hummingboard", "fsl,imx6dl"; ir_recv: ir-receiver { compatible = "gpio-ir-receiver"; gpios = <&gpio1 2 1>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_hummingboard_gpio1_2>; }; regulators { compatible = "simple-bus"; reg_3p3v: 3p3v { compatible = "regulator-fixed"; regulator-name = "3P3V"; regulator-min-microvolt = <3300000>; regulator-max-microvolt = <3300000>; regulator-always-on; }; } &i2c1 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_hummingboard_i2c1>; rtc: pcf8523@68 { compatible = "nxp,pcf8523"; reg = <0x68>; }; };
- imx6dl.dtsi文件节选
/ { aliases { /*省略无关代码*/ } soc { #address-cells = <1>; #size-cells = <1>; compatible = "simple-bus"; interrupt-parent = <&intc>; ranges; /*省略无关代码*/ timer@00a00600 { compatible = "arm,cortex-a9-twd-timer"; reg = <0x00a00600 0x20>; interrupts = <1 13 0xf01>; clocks = <&clks IMX6QDL_CLK_TWD>; }; aips-bus@02000000 { /* AIPS1 */ compatible = "fsl,aips-bus", "simple-bus"; #address-cells = <1>; #size-cells = <1>; reg = <0x02000000 0x100000>; ranges; /*省略无关代码*/ gpio1: gpio@0209c000 { compatible = "fsl,imx6q-gpio", "fsl,imx35-gpio"; reg = <0x0209c000 0x4000>; interrupts = <0 66 IRQ_TYPE_LEVEL_HIGH>, <0 67 IRQ_TYPE_LEVEL_HIGH>; gpio-controller; #gpio-cells = <2>; interrupt-controller; #interrupt-cells = <2>; }; /*省略无关代码*/ i2c1: i2c@021a0000 { #address-cells = <1>; #size-cells = <0>; compatible = "fsl,imx6q-i2c", "fsl,imx21-i2c"; reg = <0x021a0000 0x4000>; interrupts = <0 36 IRQ_TYPE_LEVEL_HIGH>; clocks = <&clks IMX6QDL_CLK_I2C1>; status = "disabled"; }; }; /*省略无关代码*/ }; };
2.1 基本构造
{}包围起来的结构称之为节点,dts中最开头的/ {},称为根节点。节点的标准结构是xxx@yyy{…},xxx是节点的名字,yyy则不是必须的,其值为节点的地址(寄存器地址或其他地址),比如i2c1: i2c@021a0000中的021a0000就是一个i2c控制器的寄存器基地址。
2.2 属性:地址
有关节点的地址,比如i2c@021a0000,虽然它在名字后面跟了地址,但是正式的设置是在reg属性中设置的比如:reg = <0x021a0000 0x4000>; reg的格式为 <address length>
,0x021a0000是寄存器基地址,0x4000是长度。address 和length的个数是可变的,由父节点的属性#address-cells 和#size-cells 决定,比如节点i2c@021a0000的父节点是aips-bus@02000000,其#address-cells 和#size-cells均为1,所以下面的i2c节点的reg属性就有一个address 和length,而i2c节点本身#address-cells 和#size-cells 分别为1和0,所以其下的rtc: pcf8523@68 的reg属性就只有一个0x68(i2c地址)了。
2.3 属性:兼容性
在.dts文件的每个设备节点中,都有一个兼容属性,兼容属性用于驱动和设备的绑定。兼容属性是一个字符串的列表,列表中的第一个字符串表征了节点代表的确切设备,形式为",",其后的字符串表征可兼容的其他设备。可以说前面的是特指,后面的则涵盖更广的范围。
如下所示:
flash@0,00000000 {
compatible = "arm,vexpress-flash", "cfi-flash";
reg = <0 0x00000000 0x04000000>,
<1 0x00000000 0x04000000>;
bank-width = <4>;
};
兼容属性的第2个字符串"cfi-flash"明显比第1个字符串"arm,vexpress-flash"涵盖的范围更广。
再如,Freescale MPC8349SoC含一个串口设备,它实现了国家半导体(National Sem-iconductor )的NS16550寄存器接口。则MPC8349串口设备的兼容属性为compatible=“fsl,mpc8349-uart”,“ns16550”。其中,fsl,mpc8349-uart指代了确切的设备,ns16550代表该设备与NS16550UART保持了寄存器兼容。因此,设备节点的兼容性和根节点的兼容性是类似的,都是“从具体到抽象”。
使用设备树后,驱动需要与.dts中描述的设备节点进行匹配,从而使驱动的probe()函数执行。对于platform_driver而言,需要添加一个OF匹配表,如前文的.dts文件的"acme,a1234-i2c-bus"兼容I2C控制器节点的OF匹配表,具体代码如下所示:
static const struct of_device_id a1234_i2c_of_match[] = {
2 { .compatible = "acme,a1234-i2c-bus", },
3 {},
4 };
5 MODULE_DEVICE_TABLE(of, a1234_i2c_of_match);
6
7 static struct platform_driver i2c_a1234_driver = {
8 .driver = {
9 .name = "a1234-i2c-bus",
10 .owner = THIS_MODULE,
11 .of_match_table = a1234_i2c_of_match,
12 },
13 .probe = i2c_a1234_probe,
14 .remove = i2c_a1234_remove,
15 };
16 module_platform_driver(i2c_a1234_driver);