Skip to main content
 首页 » 操作系统

Linux设备树(1)——先前总结

2022年07月19日25findumars

一、设备树编译

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 字节  

 三、系统通过以下步骤来生成树:

  1. CPU 经过初始化后搜索固件。

  2. 主要固件(OpenBoot、基本输入/输出系统 (Basic Input/Output System, BIOS) 或 Bootconf)初始化并创建包含已知或自标识硬件的设备树。

  3. 当主要固件在设备中发现兼容固件时,主要固件将初始化该设备并检索设备属性。

  4. 该固件将查找并引导操作系统。

  5. 内核从树的根节点开始,搜索匹配的设备驱动程序并将该驱动程序绑定到设备。

  6. 如果设备是结点,则内核会查找固件尚未检测到的子设备。内核会将所有子设备都添加到树的子树节点下面。

  7. 内核从步骤 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

参考2:http://blog.csdn.net/21cnbao/article/details/8457546(好)

补充参考:http://www.cnblogs.com/zzb-Dream-90Time/p/6474526.html


本文参考链接:https://www.cnblogs.com/hellokitty2/p/7406181.html