一、设备树编译
1.编译设备树:cd linux-x.xx & make dtbs,生成的dtb在目录linux-x.xx/arch/xxx/boot/dts下
2.反编译dtb,生成dts: linux-x.xx/scripts/dtc/dtc -I dtb -O dts xxxx.dtb -o xxxx.dts
3.将.dts编译为.dtb的工具。DTC的源代码位于内核的scripts/dtc目录,在Linux内核使能了Device Tree的情况下,编译内核的时候主机工具dtc会被编译出来,对应scripts/dtc/Makefile中的“hostprogs-y := dtc”这一hostprogs编译target。
在Linux内核的arch/arm/boot/dts/Makefile中,描述了当某种SoC被选中后,哪些.dtb文件会被编译出来。
在Linux下,我们可以单独编译Device Tree文件。当我们在Linux内核下运行make dtbs时,若我们之前选择了ARCH_VEXPRESS,上述.dtb都会由对应的.dts编译出来。因为arch/arm/Makefile中含有一个dtbs编译target项目。
4..dtb是.dts被DTC编译后的二进制格式的Device Tree描述,可由Linux内核解析。通常在我们为电路板制作NAND、SD启动image时,会为.dtb文件单独留下一个很小的区域以存放之,之后bootloader在引导kernel的过程中,会先读取该.dtb到内存。
5.对于Device Tree中的结点和属性具体是如何来描述设备的硬件细节的,一般需要文档来进行讲解,文档的后缀名一般为.txt。这些文档位于内核的Documentation/devicetree/bindings目录,其下又分为很多子目录。
6.Uboot从 v1.1.3开始支持Device Tree,其对ARM的支持则是和ARM内核支持Device Tree同期完成。
为了使能Device Tree,需要编译Uboot的时候在config文件中加入
#define CONFIG_OF_LIBFDT
在Uboot中,可以从NAND、SD或者TFTP等任意介质将.dtb读入内存,假设.dtb放入的内存地址为0x71000000,之后可在Uboot运行命令fdt addr命令设置.dtb的地址,如:
U-Boot> fdt addr 0x71000000
fdt的其他命令就变地可以使用,如fdt resize、fdt print等。
对于ARM来讲,可以透过bootz kernel_addr initrd_address dtb_address的命令来启动内核,即dtb_address作为bootz或者bootm的最后一次参数,第一个参数为内核映像的地址,第二个参数为initrd的地址,若不存在initrd,可以用 -代替。
二、设备树的组成格式
“/"代表根节点;
“model”是板的ID;
"compatible"是平台兼容,一般格式是"manufacturer,model"。内核或者uboot依靠这个属性找到相对应driver,若"compatible"出现多个属性,按序匹配driver;
“#address-cells”是address的单位(32bit),可寻址的设备使用它、#size-cells、reg在Device Tree中编码地址信息,reg中的address 和 length 字段是可变长的,父结点的#address-cells和#size-cells分别决定了子结点的reg属性的address和length字段的长度。如果root结点的#address-cells = <1>;和#size-cells = <1>;决定了serial、gpio、spi等结点的address和length字段的长度分别为1(reg中描述address和length的字段都只能有1个)。cpus 结点的#address-cells = <1>;和#size-cells = <0>;决定了2个cpu子结点的address为1,而length为空。可以理解为地址需要几个维度来描述,如片选0的偏移0地址处,就需要#address-cells为2.
“#size-cells”是length的单位(32bit);
"reg"是寄存器,格式是"<address,length>",作为平台内存资源,组织形式为reg = <address1 length1 [address2 length2] [address3 length3] ... >,length则为cell的列表或者为空(若#size-cells = 0)
"aliase" 是别名,必须节点全称,可以通过地址引用获取;
”chosen“是板级启动参数;
"cpus"是SOC的CPU信息,可以改变运行频率或者开关CPU,命名遵循的组织形式为:<name>[@<unit-address>],多个相同类型设备结点的name可以一样,只要unit-address不同即可,如本例中含有cpu@0、cpu@1,设备的unit-address地址也经常在其对应结点的reg属性中给出。
"memory"是板级内存的信息。
"interrupts"是中断控制器,根据SOC自定义格式,这里是<输入类型 中断号 触发方式>,作为平台中断资源;输入类型:0是SPI中断,1是PPI中断;触发类型:0上升沿,2下降沿(对SPI无效),4高电平,8低电平(对SPI无效),见Documentation\devicetree\bindings\interrupt-controller\arm,gic.txt
“interrupt-controller”指示这个节点是中断控制节点,它的属性为空,中断控制器应该加上此属性表明自己的身份(直接在{}中写上interrupt-controller即可)
“interrupt-cells”与#address-cells 和 #size-cells相似,它表明连接此中断控制器的设备的interrupts属性的cell大小
"interrupt-parent"设备结点透过它来指定它所依附的中断控制器的phandle,当结点没有指定interrupt-parent 时,则从父级结点继承。
"[label:]"如gic: interrupt-controller@1c81000,这个标签可以作为地址赋值到其他节点的属性;
“device_type":设备类型,寻找节点可以依据这个属性;
"status"是开关节点设备的状态,取值"okay"或者"ok"表示使能,"disabled"表示失能。
“ranges” 经过总线桥后的address往往需要经过转换才能对应的CPU的memory映射。external-bus的ranges属性定义了经过external-bus桥后的地址范围如何映射到CPU的memory区域。ranges是地址转换表,其中的每个项目是一个子地址、父地址以及在子地址空间的大小的映射。映射表中的子地址、父地址分别采用子地址空间的#address-cells和父地址空间的#address-cells大小。
例如:ranges = <0 0 0x10100000 0x10000 将子地址空间的片选0的0偏移地址处映射到地址0x10100000处,映射大小为0x10000 字节
1 0 0x10160000 0x10000>; 将子地址空间的片选1的0偏移地址处映射到地址0x10160000处,映射大小为0x10000 字节
三、系统通过以下步骤来生成树:
-
CPU 经过初始化后搜索固件。
-
主要固件(OpenBoot、基本输入/输出系统 (Basic Input/Output System, BIOS) 或 Bootconf)初始化并创建包含已知或自标识硬件的设备树。
-
当主要固件在设备中发现兼容固件时,主要固件将初始化该设备并检索设备属性。
-
该固件将查找并引导操作系统。
-
内核从树的根节点开始,搜索匹配的设备驱动程序并将该驱动程序绑定到设备。
-
如果设备是结点,则内核会查找固件尚未检测到的子设备。内核会将所有子设备都添加到树的子树节点下面。
-
内核从步骤 5 开始重复该过程,直到无需再创建设备节点。
主要相关函数在/drivers/of/base.c中,使用上比较打的区别是platform_driver结构被替换为了of_device_id ,而platform_driver不变
四、设备与确定的匹配
.dts文件中,root结点"/"的compatible 属性定义了系统的名称,它的组织形式为:<manufacturer>,<model>。Linux内核通过它判断启动的是什么machine。
.dts文件的每个设备,都有一个compatible 属性,compatible属性用户驱动和设备的绑定。compatible 属性是一个字符串的列表(第一个“”内的字符串),列表中的第一个字符串表征了结点代表的确切设备,形式为"<manufacturer>,<model>",其后的字符串表征可兼容的其他设备。可以说前面的是特指,后面的则涵盖更广的范围。如compatible = "arm,vexpress-flash", "cfi-flash"; 在与驱动进行绑定主要靠mode域,前面的manufacturer不重要。
五、常用的of API
1.判断设备结点的compatible 属性是否包含compat指定的字符串
int of_device_is_compatible(const struct device_node *device,const char *compat)
2.根据compatible属性,遍历Device Tree中所有的设备结点,获得设备结点
struct device_node *of_find_compatible_node(struct device_node *from,const char *type, const char *compatible)
3. 读取设备结点np的属性名为propname,类型为8、16、32、64位整型数组的属性。对于32位处理器来讲,最常用的是of_property_read_u32_array()
of_property_read_u32_array() / of_property_read_u64_array()还有u8,u16的
4.前者读取字符串属性,后者读取字符串数组属性中的第index个字符串
of_property_read_string() / of_property_read_string_index()
5.如果设备结点np含有propname属性,则返回true,否则返回false。一般用于检查空属性是否存在。
of_property_read_bool()
6.通过设备结点直接进行设备内存区间的 ioremap(),index是内存段的索引。若设备结点的reg属性有多段,可通过index标示要ioremap的是哪一段,只有1段的情况,index为0。采用Device Tree后,大量的设备驱动通过of_iomap()进行映射,而不再通过传统的ioremap。
void __iomem *of_iomap(struct device_node *np, int index)
7.透过Device Tree或者设备的中断号,实际上是从.dts中的interrupts属性解析出中断号。若设备使用了多个中断,index指定中断的索引号。
irq_of_parse_and_map
还有一些OF API,这里不一一列举,具体可参考include/linux/of.h头文件。
六、总结
ARM社区一贯充斥的大量垃圾代码导致Linus盛怒,因此社区在2011年到2012年进行了大量的工作。ARM Linux开始围绕Device Tree展开,Device Tree有自己的独立的语法,它的源文件为.dts,编译后得到.dtb,Bootloader在引导Linux内核的时候会将.dtb地址告知内核。之后内核会展开Device Tree并创建和注册相关的设备,因此arch/arm/mach-xxx和arch/arm/plat-xxx中大量的用于注册platform、I2C、SPI板级信息的代码被删除,而驱动也以新的方式和.dts中定义的设备结点进行匹配。
补充:
flash_SY7803:flashlight { compatible = "qcom,leds-gpio-flash"; //匹配参数 status = "okay"; pinctrl-names = "flash_default"; pinctrl-0 = <&SY7803_default>; qcom,flash-en = <&msm_gpio 31 0>; qcom,flash-now = <&msm_gpio 32 0>; qcom,op-seq = "flash_en", "flash_now"; qcom,torch-seq-val = <0 1>; qcom,flash-seq-val = <1 0>; linux,name = "flashlight"; //属性 linux,name linux,default-trigger = "flashlight-trigger"; };
使用: 在代码中获取节点的所有信息
0.先把节点获取到 struct device_node *np = NULL; np = of_find_node_by_path("/test_nod@12345678");
1.of_get_named_gpio(node, "qcom,flash-en", 0);返回31;
2.获取结点中的属性:of_find_property()
3.读取到属性中的整数的数组:uint32_t array_flash_seq[2]; rc = of_property_read_u32_array(node, "qcom,flash-seq-val",array_flash_seq, 2); ==》array_flash_seq <1 0>
4.读取到属性中的字符串的数组:rc = of_property_read_string_index(node, "qcom,op-seq", i, &seq_name); //"flash_en", "flash_now";
5.获取到中断的号码:irqno = irq_of_parse_and_map(np, 0);
6.可以使用ret = request_irq验证中断号码是否有效
7.获取设备属性名字:of_property_read_string(node, "linux,name", &flash_led->cdev.name) // flash_led->cdev.name = “flashlight ”;
1. 设备树中 compatible
键值对
2.driver中
platform_driver 结构体
probe
remove
of_match_table
probe 中
1.通过of函数获得相关的资源信息,
2. 申请引脚信息 pinctrl
3.注册设备 classdev
参考1:http://www.cnblogs.com/kevinhwang/p/5647021.html