设备树总结

参考:
https://blog.csdn.net/ethercat_i7/article/details/83786670
https://www.cnblogs.com/god-of-death/archive/2004/01/13/10270453.html
https://zhuanlan.zhihu.com/p/143167176

一、基本概念

DTS即Device Tree Source,是一个文本形式的文件,用于描述硬件信息,包括CPU的数量和类别、内存基地址和大小、中断控制器、总线和桥、外设、时钟和GPIO控制器等。
DTB即Device Tree Blob,是一个二进制形式的文件,由linux内核识别,为其中的设备匹配合适的驱动程序。
DTC即Device Tree Compiler,将适合人类阅读和编辑的DTS文件编译成适合机器处理的DTB文件。

DTS(Device tree syntax,另一种说法是Device tree source)是设备树源文件,为了方便阅读及修改,采用文本格式。DTC(Device tree compiler)是一个小工具,负责将DTS转换成DTB(Device tree blob)。DTB是DTS的二进制形式,供机器使用。使用中,我们首先根据硬件修改DTS文件,然后在编译的时候通过DTC工具将DTS文件转换成DTB文件,然后将DTB文件烧写到机器上(如emmc,磁盘等存储介质)。系统启动时,fastboot(或者类似的启动程序,如Uboot)在启动内核前将DTB文件读到内存中,跳转到内核执行的同时将DTB起始地址传给内核。内核通过起始地址就可以根据DTB的结构解析整个设备树。说设备树的规范可以分成两个层次,是针对DTS的,关于DTB的结构不在此范围内。DTB仅仅是为了方便机器使用而对DTS的转换而已(也可以说DTS仅是为了方便人类使用而对DTB的一种描述)。在这里插入图片描述
如上图所示,bootloader读取dtb文件放入RAM中,并将存放地址告诉linux内核,内核启动以后从该地址读取相应的设备信息,匹配平台和设备驱动。

设备树(Device tree)是一套用来描述硬件属相的规则。ARM Linux采用设备树机制源于2011年3月份Linux创始人Linus Torvalds发的一封邮件,在这封邮件中他提倡ARM平台应该参考其他平台如PowerPC的设备树机制描述硬件。因为在此之前,ARM平台还是采用旧的机制,在kernel/arch/arm/plat-xxx目录和kernel/arch/arm/mach-xxx目录下用代码描述硬件,如注册platform设备,声明设备的resource等。因为这些代码都是用来描述芯片平台及板级差异的,所以对于内核来讲都是垃圾代码。

设备树是从软件使用的角度描述硬件的,不是从硬件设计的角度描述的。我们在写设备树时没有必要按照硬件逻辑生搬硬套,也不要指望通过阅读设备树弄清楚硬件是如何设计的。对于软件可以自动识别的硬件,如USB设备,PCI设备,也是没有必要通过设备树描述的。

本质上,Device Tree改变了原来用code方式将HW配置信息嵌入到内核代码的方法,改用bootloader传递一个DB的形式。对于嵌入式系统,在系统启动阶段,bootloader会加载内核并将控制权转交给内核。

设备树首先是一个树形结构,并且是一棵树。除了根节点外其他子节点都有唯一的父节点,节点下可以有子节点和属性(子节点可以看成是树枝,属性可以看成是叶子)。属性由名字和值组成(名字是必须的,但是值不是必须的,如果只要根据是否存在这个属性就可以表示我们想要的功能,那么可以不需要有值)。节点下的属性用来表示节点的特性,子节点和父节点具有一定的从属关系。真实的硬件不可能是这样规则的树形结构,所以设备树仅是软件开发人员为了描述硬件而做的一个近似表示而已,连抽象都算不上。

二、节点

首先说节点的表示方法,除了根节点只用一个斜杠“/”表示外,其他节点的表示形式如“node-name@unit-address”。@前边是节点名字,后边是节点地址。节点名字的长度范围是1到31。

规范要求节点名字应该以字母开头,虽然允许后边的位置使用非字母的字符,但实际情况我们实在没必要使用其他字符,一般情况全部用字母表示就够了。特别是规范建议在起名字时采用通用的名字而不是专有的名字,比如对于网卡,使用ethernet表示就可以了,可以通过地址区分不同的网卡,网卡的区别可以通过节点下的属性区分。

节点地址是用来区别同名节点的,不是软件意义上的地址,但是有些情况可以用软件地址作为这个地址。比如两个I2C控制器的名字可以都是i2c,然后用控制器寄存器首地址作为节点地址。对于cpu,因为它是没有寄存器地址的,就可以用核的号码作为地址,对于8核处理器,地址可以从0到7。ePAPR规范中关于节点地址的描述不太好理解,原文是“The unit-address component of the name is specific to the bus type on which the node sits”。其实我觉得这句话说的也不太准确,因为并不是所有节点表示的硬件都位于某个总线上,比如内存,cpu。设备树是软件对硬件的一种近似表示,软件需要他怎么表示,他就怎么表示。对于cpu,软件需要序号,那么地址就用序号,对于i2c控制器,软件需要寄存器首地址,那么就用地址。

除此之外规范还要求,如果节点有地址,那么节点下边必须有一个叫reg的属性,并且该地址必须和reg的属性的第一个地址相同。如果节点没有reg属性,那么节点地址及前边的@必须都不能有。

关于属性和值我们还没开始介绍,这里插一句,其实reg就是register的缩写,属性主要用来表示控制器寄存器首地址的。我觉得规范中这一条不是非常有必要,因为有些设备只要有一个地址就够了,那么放在节点地址中就够了,完全没必要非得再加个reg属性。在这一段的最后一句,规范来了句“The binding for a particular bus may specify additional, more specific requirements for the format of reg and the unit-address.”,我觉得这句话的意思和“设备树是软件对硬件的一种近似表示,软件需要他怎么表示,他就怎么表示”差不多,没有什么玄的。

节点路径也比较容易理解,从根节点到每个节点都可以形成一个路径,如第一节的例子/cpus/cpu@0,通过这个可以唯一的表示cpu@0这个节点。因为cpu@0在cpus下边是唯一的,但是在整个设备树可能不是唯一的,只有用全路径表示才能毫无异议的确认是哪个节点。如果省略了节点地址也不会产生歧义,那么可以省略不写。就像编程中的括号一样,个人觉得这个没必要省。

除了名字和地址外,节点前边还可以有一个标签(label),这个标签不是必须的,一般只有在别个地方需要引用这个节点时才会用标签标示这个节点,因为如果用全路径太繁琐了。如“i2c_1: i2c@12C70000”中的i2c_1就是一个标签。

三、属性

device_type = "memory"就是一个属性,等号前边是属性,后边是值。节点是一个逻辑上相对独立的实体,属性是用来描述节点特性的,根据需要一个节点由0个,1个或多个属性表示节点的特性。一个属性由名字和值两部分组成。

和节点的名字类似,规范要求属性名字由1到31个字符组成。和节点名字字符的种类有些区别,不允许有大写字母,增加了问号和井号两个字符。不清楚为什么没有和节点名字完全保持一致,井号对于初学者容易误解,以为是注释。

属性的值在内存中由0个或多个字节存储。标准定义的基本类型包括:空,u32,u64,字符串,prop-encoded-array,字符数组6种。前边我们已经提到,当不需要值就可以表示节点的特性时,属性的值可以为空。u32,u64,字符串,字符数组和c语言的定义没有区别,注意的是规范要求都是大端表示,字符串也是以0x00结尾。prop-encoded-array是一个结构体数组,数组的元素具体是什么根据属性的定义确定,后边我们讲到具体的属性时会详细说明。规范中还有一个类型的属性值,叫phandle,这个类型的属性在内存中存储时本质上是u32。

规范预定义了一些标准的属性。“compatible”,“model”,"device_type"都是用来表示节点基本信息的。

“compatible”属性是用来匹配驱动的,它的类型是字符串数组,每个字符串表示一种设备的类型,从具体到一般。举个例子就比较清楚了,比如某个串口控制器节点的属性”compatible = “fsl,mpc8641-uart”, “ns16550"“。第一个字符串“fsl,mpc8641-uart”前边部分是厂商(推测是frescale),后边部分是控制器具体型号,这个形式也是规范建议的标准写法。第二个字符串ns16550表示一类符合同一标准的串口控制器,比第一个字符串表示的范围更大。内核匹配驱动时首先看是否有匹配第一个字符串的驱动,如果没有的话再匹配第二个(如果有更多的,依次类推,所以优先匹配前边的)。

"model"属性用来表示设备的型号,用字符串表示,不像"compatible"用多个字符串,只需一个就够了。"device_type"属性用来表示设备类型,用字符串表示。

“#address-cells”,"#size-cells",“reg”,“ranges”,"dma-ranges"属性都是和地址有关的。

不同的平台,不同的总线,地址位长度可能不同,有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。

reg属性用来表示节点地址资源的,比如常见的就是寄存器的起始地址及大小。要想表示一块连续地址,必须包含起始地址和空间大小两个参数,如果有多块地址,那么就需要多组这样的值表示。还记得前边说过的prop-encoded-array类型的属性吧,就是用来干这个的,他表示一个数组,每个元素的具体格式根据属性而定,reg属性的每个元素是一个二元组,包含起始地址和大小。还有另外一个问题,地址和大小用几个u32表示呢?这个就由父节点的"#address-cells","#size-cells"属性确定

总线上设备在总线地址和总线本身的地址可能不同,ranges属性用来表示如何转换。和reg属性类似,ranges属性也是prop-encoded-array类型的属性,不同的是ranges属性的每个元素是三元组,按照前后顺序分别是(子总线地址,父总线地址,大小)子总线地址需要几个u32表示由ranges属性所在节点(当前节点)的#address-cells属性决定,父总线地址需要几个u32表示由上一级节点的#address-cells属性决定,大小需要几个u32表示由当前节点的’#size-cells’属性确定。
根节点是从cpu的角度来描述地址空间的,根节点的子节点总是依赖于cpu的地址域,所以不需要显示地地址映射。比如, serial@101f0000 设备描述的地址就是0x101f0000.

但是有些节点并不是根节点的直接子节点,因此其不使用cpu地址域。为了可以得到设备的映射地址,设备树必须说明一个域到另一个作用域的内存地址的转换关系,range属性就此诞生。

下面是一个简单的例子:

/ {
    compatible ="acme,coyotes-revenge";

    #address-cells= <1>;

    #size-cells =<1>;

    ...

    external-bus {
       #address-cells = <2>

        #size-cells= <1>;

       ranges = <0 0 0x10100000   0x10000     // Chipselect 1, Ethernet

                  1 0  0x10160000  0x10000     // Chipselect 2, i2ccontroller

                  2 0  0x30000000  0x1000000>; // Chipselect 3, NOR Flash
//zrw:第一列的0 1 2是片选号,和第二列共同组成子地址,应该顺着写就行;第三列是父节点基地址;第四列是子地址空间长度。这里的第一、二、四列分别对应下面各个reg的三列。ranges实际上做的就是把一些不在cpu地址空间的设备挂到了指定的cpu地址空间。

        ethernet@0,0{
           compatible = "smc,smc91c111";

            reg =<0 0 0x1000>;

        };

 

        i2c@1,0 {
           compatible = "acme,a1234-i2c-bus";

           #address-cells = <1>;

           #size-cells = <0>;

            reg =<1 0 0x1000>;

            rtc@58 {
               compatible = "maxim,ds1338";

                reg= <58>;

            };

        };

 

        flash@2,0 {
           compatible = "samsung,k8f1315ebm", "cfi-flash";

            reg =<2 0 0x4000000>;

        };

    };

};

ranges属性是说明地址转换的一个列表,列表的每一个条目分别表示子节点的地址,父节点的地址,子节点地址空间的长度(内存空间大小),每个条目的个数分别是用子节点的 #address-cells 值,父节点#address-cells 值,以及子节点 #size-cells 的值来表示。

· 比如,就上面出显得设备树代码来讲,子节点的#address-cells为2,父节点#address-cells为1,子节点#size-cells 为1,则上面代码中程序先的ranges属性的含义就是:

· 片选号为0,偏移量为0的设备映射的地址为0x10100000…0x1010ffff

· 片选号为1,偏移量为0的设备映射的地址为0x10160000…0x1016ffff

· 片选号为2,偏移量为0的设备映射的地址为0x30000000…0x30ffffff

有的时候在一个节点中需要引用另外一个节点,比如某个外设的中断连在哪个中断控制器上。在讲节点那一节我们说过,可以通过节点的全路径指定是哪个节点,但这种方法非常繁琐。phandle属性是专门为方便引用节点设计的,想要引用哪个节点就在该节点下边增加一个phandle属性,设定值为一个u32,如’phandle = <1>’,引用的地方直接使用数字1就可以引用该节点,如’interrupt-parent = <1>’。以上是规范中描述的方法,实际上这样也不方便,我在实际的代码中没有看到这么用的。还记得节点那节说过节点名字前边可以定义一个标签吧,实际情况是都用标签引用,比如节点标签为intc1,那么用’interrupt-parent = <&intc1>'就可以引用了

status属性用来表示节点的状态的,其实就是硬件的状态,用字符串表示。‘okay’表示硬件正常工作,“disabled”表示硬件当前不可用,“fail”表示因为出错不可用,“fail-sss”表示因为某种原因出错不可用,sss表示具体的出错原因。实际中,基本只用’okay’和’disabled’。

四、中断

中断一般包括中断产生设备和中断处理设备。中断控制器负责处理中断,每一个中断都有对应的中断号及触发条件。中断产生设备可能有多个中断源,有时多个中断源对应中断控制器中的一个中断,这种情况中断产生设备的中断源称之为中断控制器中对应中断的子中断。一般情况中断产生设备数量要多于中断控制器,多个中断产生设备的中断都由一个中断控制器处理,这种多对一的关系也很像一个树形结构,所以在设备树中,中断也被描述成树,叫中断树。以下表述的时候为了明确是在说中断树,在父节点和子节点前边我们都加上“中断”二字,是为了防止和设备树的父节点、子节点混淆(虽然大部分情况设备树的父子关系就是中断树的父子关系,但是因为存在特例,所以我们还是强调是中断父子关系)。

中断产生设备用interrupts属性描述中断源(interrupt specifier),因为不同的硬件描述中断源需要的数据量不同,所以interrupts属性的类型也是prop-encoded-array。为了明确表示一个中断源由几个u32表示,又引入了#interrupt-cells属性,#interrupt-cells属性的类型是u32,假如一个中断源需要2个u32表示(一个表示中断号,另一个表示中断类型),那么#interrupt-cells就设置成2。有些情况下,设备树的父节点不是中断的父节点(主要是中断控制器一般不是父节点),为此引入了interrupt-parent属性,该属性的类型是phandle,用来引用中断父节点(我们前边说过,一般用父节点的标签,这个地方说中断父节点而不是中断控制器是有原因的)。interrupt-parent属性用来指定当前设备的Interrupt Controllers/Interrupt Nexus如果设备树的父节点就是中断父节点,那么可以不用设置interrupt-parent属性interrupts属性和interrupt-parent属性都是中断产生设备节点的属性(类似于叶子),但是#interrupt-cells属性不是,#interrupt-cells属性是中断控制器节点以及interrupt nexus节点的属性(类似于树根和树枝,interrupt nexus应该是直接和中断控制器相连,用来记录连接到中断控制器的属性,中间层的感觉),这两类节点都可能是中断父节点。

中断控制器节点用interrupt-controller属性表示自己是中断控制器,这个属性的类型是空,不用设置值,只要存在这个节点就表示该节点是中断控制器。除了这个属性外,中断控制器节点还有#interrupt-cells属性,用来表示该中断控制器直接管理下的interrupt domain(后边我们会讲中断控制器的中断子节点interrupt nexus节点有单独的interrupt domain)用几个u32表示一个中断源(interrupt specifier)。中断控制器节点就包括interrupt-controller和#interrupt-cells两个关于中断的属性。中断控制器的#address-cells属性和中断映射有关系,但是该属性不是为中断设计的,中断映射只是用到了这个属性而已。

前边说中断控制器中的一个中断可能对应中断产生设备中的多个中断源,那这种对应关系用什么描述呢?我们还说过#interrupt-cell属性不仅是中断控制器节点的属性,还是interrupt nexus节点的属性,这个interrupt nexus节点就是描述中断映射关系的,该节点通过interrupt-map,interrupt-map-mask属性描述中断映射关系。interrupt-map属性是prop-encoded-array类型的,每个元素表示一个中断映射关系(注意是一个"中断映射关系",不是"一个中断"映射关系),interrupt-map属性从前向后包括:中断子设备地址,中断子设备中断源(interrupt specifier),中断父设备,中断父设备地址,中断父设备中断源(interrupt specifier)五部分。中断子设备地址具体由几个u32组成是由中断子设备所在总线(不是中断父设备)的#address-cells属性决定的,这个地方为什么用中断设备地址而不用中断设备的phandle,是有原因的,因为中断设备会用interrupt-parrent属性指向中断父节点,所以中断子设备是可以确定的,不需要说明。还因为中断子设备地址可以做与运算,通过interrupt-map-mask属性就可以实现多对一的映射。中断子设备中断源(interrupt specifier)由几个u32组成是由该interrupt nexus节点下的#interrupt-cell决定的。中断父设备是一个指向中断父设备的phandle属性,一般情况下是中断控制器,但是按照中断树的逻辑,也可能是更高一级的interrupt nexus节点。中断父设备地址具体由几个u32组成是由中断父设备节点下的#address-cells属性决定的(注意,不是中断父设备所在总线的#address-cells属性)。中断父设备中断源(interrupt specifier)由几个u32组成是由中断父设备的#interrupt-cells属性决定的。

还记得前边说过中断设备的中断源和中断控制器的中断源可能是多对一的关系,如果每个子中断都用interrupt-map中的一行表示,那么interrupt-map属性将非常大。为了让多个子中断共享映射关系,引入了interrupt-map-mask属性,该属性的类型也是prop-enacoded-array,interrupt-map-mask属性包含中断子设备地址和中断子设备中断源的bit mask给定一个子中断源,那么首先和interrupt-map-mask做与运算,运算结果再通过interrupt-map属性查找对应的中断父设备中断源。这就是我们前边为什么说interrupt-map属性的一行是一个“中断映射关系”,而不是“一个中断”映射关系的原因。

我们再来复习一下,整个中断树的最底层是中断产生设备(也可能是从interrupt nexus节点),中断产生设备用interrupts属性描述他能产生的中断。因为他的中断父设备可能和设备树的父设备不同,那么用interrupt-parent属性指向他的中断父设备。他的中断父设备可能是中断控制器(如果中断产生设备的中断和中断控制器的中断是一一对应的,或者最底层是interrupt nexus节点),也可能是interrupt nexus节点(如果最底层是中断产生设备,且需要映射)。interrupt nexus节点及他的所有直接子节点构成了一个interrupt domain,在该interrupt domain下中断源怎样表示由#interrupt-cells属性决定,如何由中断子设备中断源找到中断父设备中断源由interrupt-map和interrupt-map-mask属性决定interrupt nexus的父节点可能还是一个interrupt nexus父节点,也可能是一个中断控制器,当向上找到最后一个中断控制器,并且该中断控制器再也没有中断父设备时,整个中断树就遍历完成了。中断控制器用interrupt-controller属性表示自己是中断控制器,并且用#interrupt-cells属性表示他所直接管理的interrupt domain用几个u32表示一个中断源。根据中断树的特性,一个设备树中是有可能有多个中断树的。

以上是中断在设备树中如何描述的规则,听起来是挺复杂的,但只要理解了就很简单,为了帮助理解我们举一个实际的例子。为了突出中断部分,我们做了简化。

/ {
    model = "Marvell Armada 375 family SoC";
    compatible = "marvell,armada375";
    soc {
        #address-cells = <2>;
        #size-cells = <1>;
        interrupt-parent = <&gic>;

        internal-regs {
            compatible = "simple-bus";
            #address-cells = <1>;
            #size-cells = <1>;

            timer@c600 {
                compatible = "arm,cortex-a9-twd-timer";
                reg = <0xc600 0x20>;
                interrupts = <GIC_PPI 13 (IRQ_TYPE_EDGE_RISING | GIC_CPU_MASK_SIMPLE(2))>;
                clocks = <&coreclk 2>;
            };

            gic: interrupt-controller@d000 {
                compatible = "arm,cortex-a9-gic";
                #interrupt-cells = <3>;
                #address-cells = <0>;
                interrupt-controller;
                reg = <0xd000 0x1000>,
                      <0xc100 0x100>;
            };
        }

        pcie-controller {
            compatible = "marvell,armada-370-pcie";
            #address-cells = <3>;
            #size-cells = <2>;

            pcie@1,0 {
                #address-cells = <3>;
                #size-cells = <2>;
                #interrupt-cells = <1>;
                interrupt-map-mask = <0 0 0 0>;
                interrupt-map = <0 0 0 0 &gic GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
            };
        };
}

首先我们看到timer@c600这个设备节点下定义了interrupts属性,这说明该设备可以产生中断,但是这个属性下描述了几个中断我们是看不出来的(如果有经验了,我们能猜出只是一个中断,现在我们按照规则确认)。因为该节点没有interrupt-parent属性,那么认为设备树的父节点internal-regs就是中断父节点,在internal-regs父节点下还是没有interrupt-parent属性,那么还是继续找设备树父节点,找到了soc,在该节点下边有interrupt-parent属性。该属性引用的标签为gic,搜索整个设备树,interrupt-controller@d000的标签为gic。gic节点下有interrupt-controller属性,说明他是一个中断控制器。gic节点还有属性#interrupt-cells = <3>,说明在该控制器的interrupt domain下,中断源(interrupt specifier)用3个u32表示,我们再看timer@c600下的interrupts属性也确实由3个u32组成(可以参考GIC的规范,第一个u32表示中断类型,第二个是中断号,第三个是中断触发条件)。这个例子说明如果中断产生设备的中断源和中断控制器的中断源是一一对应的,那么可以不需要interrupt nexus节点及相关的属性来表示中断映射

再看pcie@1,0这个节点,有#interrupt-cells属性,但是没有interrupt-controller属性,这说明他是一个interrupt nexus节点。该节点的**#interrupt-cells属性为1,说明该interrupt nexus节点管辖下的中断源用1个u32表示就可以了**。在pcie@1,0节点下边没有子节点,且也没有节点的interrupt-parent属性指向pcie@1,0节点,所以从设备树上看不到该interrupt domain下的中断产生设备,可能的原因是这些中断产生设备软件可以动态识别所以不需要设备树描述。因为interrupt-map-mask属性是由中断产生设备的地址和中断源(interrupt specifier)组成,且中断源用1个u32表示,那么可以推测中断产生设备地址由3个u32组成。这里需要注意的是pcie@1,0节点的#address-cells属性为3,是说该总线下边的设备地址用3个u32表示,但并不代表中断产生设备的设备地址也一定3个u32表示,此处不能说是巧合,但是我们要清楚中断产生设备的地址由几个u32组成是由该设备所在总线决定的,对于pcie总线也确实是3,但是其他总线可能存在其他种的情况。现在我们来分析interrupt-map属性,前三个数字是中断设备地址,第四个数字是中断设备的中断源。因为interrupt-map-mask是全0,这样不管与什么数字做与运算结果都是0,interrupt-map属性的前4个数字也都是0,这说明在pcie@1,0下边所有的中断映射到中断父节点的中断都是一个中断。接着是指向gic的,因为gic节点下#address-cells属性为0,所以后边不需要描述中断父设备的地址了,后边3个数字都是表示中断父设备中断源的。一句话描述就是pcie@1,0下的所有中断都映射到gic,GIC_SPI类型的第29号中断,触发类型为高电平触发。这个例子说明在中断树的最下边可以是interrupt nexus节点。

以上例子中断树的根是gic,gic下边有两个孩子,一个是中断设备timer@c600,一个是interrupt nexus节点pcie@1,0。gic直接管辖的interrupt domain用3个u32表示中断源,timer@c600在这个interrupt domain下。pcie@1,0下定义了一个新的interrupt domain,在该interrupt domain下,中断源用1个u32表示,pcie@1,0用interrupt-map和interrupt-map-mask属性将下边所有设备的中断映射到一个gic下边的中断上。

五、根节点

一个最简单的设备树必须包含根节点,cpus节点,memory节点。根节点的名字及全路径都是“/”,至少需要包含model和compatible两个属性。model属性我们在属性那节已经说过是用来描述产品型号的,类型为字符串,推荐的格式为“manufacturer,model-number”(非强制的)。根节点的model属性描述的是板子的型号或者芯片平台的型号,如:
model = “Atmel AT91SAM9G20 family SoC”
model = “Samsung SMDK5420 board based on EXYNOS5420”

从软件的层面讲model属性仅仅表示一个名字而已,没有更多的作用。compatible属性则不同,该属性决定软件如何匹配硬件对硬件进行初始化。属性那一节我们说过compatible属性的类型是字符串数组,按照范围从小到大的顺序排列,每个字符串表示一种匹配类型。根节点的compatible属性表示平台如何匹配,比如‘compatible = “samsung,smdk5420”, “samsung,exynos5420”, “samsung,exynos5”’,表示软件应该首先匹配’samsung,smdk5420’,这个是一款开发板。如果无法匹配,再试着匹配"samsung,exynos5420",这个是一款芯片平台。如果还是无法匹配,还可以试着匹配 “samsung,exynos5”,这是一个系列的芯片平台。这里说的匹配是指软件根据该信息找到对应的代码,如对应的初始化函数。

根节点表示的是整个板子或者芯片平台,所以在系统初始化比较早的时候就需要确认是什么平台,怎样初始化。对于Linux,是通过在start_kernel函数调用setup_arch函数实现的。不同的架构,setup_arch函数的实现不同,对于arm架构,setup_arch函数源代码位于arch/arm/kernel/setup.c中。

六、memory&chosen节点

简单的设备树也必须包含cpus节点和memory节点。memory节点用来描述硬件内存布局的。如果有多块内存,既可以通过多个memory节点表示,也可以通过一个memory节点的reg属性的多个元素支持。举一个例子,假如某个64位的系统有两块内存,分别是

• RAM: 起始地址 0x0, 长度 0x80000000 (2GB)
• RAM: 起始地址 0x100000000, 长度 0x100000000 (4GB)

对于64位的系统,根节点的#address-cells属性和#size-cells属性都设置成2。一个memory节点的形式如下(还记得前几节说过节点地址必须和reg属性第一个地址相同的事情吧):
memory@0 {
device_type = “memory”;
reg = <0x000000000 0x00000000 0x00000000 0x80000000
0x000000001 0x00000000 0x00000001 0x00000000>;
};

两个memory节点的形式如下:
memory@0 {
device_type = “memory”;
reg = <0x000000000 0x00000000 0x00000000 0x80000000>;
};
memory@100000000 {
device_type = “memory”;
reg = <0x000000001 0x00000000 0x00000001 0x00000000>;
};

chosen节点也位于根节点下,该节点用来给内核传递参数(不代表实际硬件)。对于Linux内核,该节点下最有用的属性是bootargs,该属性的类型是字符串,用来向Linux内核传递cmdline。规范中还定义了stdout-path和stdin-path两个可选的、字符串类型的属性,这两个属性的目的是用来指定标准输入输出设备的,在linux中,这两个属性基本不用。

memory和chosen节点在内核初始化的代码都位于start_kernel()->setup_arch()->setup_machine_fdt()->early_init_dt_scan_nodes()函数中(位于drivers/of/fdt.c)

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值