HI3519V101的设备树dts分析

ARM 431浏览

1.目录分析

/hisilicon/hi3519v101/hi3519v101/osdrv/opensource/kernel/linux-3.18.y/arch/arm/boot/dts

我们看到有四个文件。在linux驱动开发编程模块中我们知道:

.dts文件是一种ASCII 文本格式的Device Tree描述,此文本格式非常人性化,适合人类的阅读习惯。基本上,在ARM Linux中,一个.dts文件对应一个ARMmachine,一般放置在内核的arch/arm/boot/dts/目录。由于一个SoC可能对应多个machine(一个SoC可以对应多个产品和电路板),势必这些.dts文件需包含许多共同的部分,Linux内核为了简化,把SoC公用的部分或者多个machine共同的部分一般提炼为.dtsi,类似于C语言的头文件。其他的machine对应的.dtsinclude这个.dtsi。譬如,对于HI3519V101而言, hisi-hi3519v101.dtsi就被hisi-hisi-hi3519v101-hmp-demb.dts所引用。

我们看到关于HI3519V101的设备树文件有四个,我们知道一个dts对应一个machine,这里有两个dts,也就是对应两个machine。我们从/dts下的Makefile可以看到添加了哪一个:

可以看到我们使用的是hisi-hi3519v101-hmp-demb.dts。顺便可以看下对hisi-hi3519v101-demb.dts有没有包含关系,实际看到貌似没有。因此后面我们主要分析hisi-hi3519v101.dtsi和hisi-hi3519v101-hmp-demb.dts文件。

注:如果看过内核/arch/arm/boot/dts目录的读者看到这可能有一个疑问。在每个.dsti.dts中都会存在一个“/”根节点,那么如果在一个设备树文件中include一个.dtsi文件,那么岂不是存在多个“/”根节点了么。其实不然,编译器DTC在对.dts进行编译生成dtb时,会对node进行合并操作,最终生成的dtb只有一个root nodedtc会进行合并操作这一点从属性上也可以得到验证。这个稍后做讲解。

2. hisi-hi3519v101.dtsi

#include "skeleton.dtsi"

/arch/arm/boot/dts/目录中有一个文件skeleton.dtsi,该文件为各ARM vendor共用的一些硬件定义信息。以下为skeleton.dtsi的全部内容。Skeleton的作用是定义设备启动所需要的最小的组件,它定义了root-node下的最基本且必要的child-node类型,通常对应SoC上的基础设施如CPU,Memory等。

如上,属性# address-cells的值为1,它代表以“/”根节点为parent的子节点中,reg属性中存在一个address值;#size-cells的值为1,它代表以“” 根节点为parent的子节点中,reg属性中存在一个size值。即父节点的# address-cells#size-cells决定了子节点的addresssize的长度 我们后面再详细探究这两个属性的含义。下面我们看下代码中的实现:

#include <dt-bindings/clock/hi3519-clock.h>

包含了dt-bindings文件下的东西。dt-bindings下是一些头文件。

/ {

    aliases {

        serial0 = &uart0;

        i2c0 = &i2c_bus0;

        …………………………………….

        gpio16 = &gpio_chip16;

    };

aliases node用来定义别名,类似C++中引用。上面是一个在.dtsi中的典型应用,当使用i2c0时,也即使用i2c_bus0,使得引用节点变得简单方便。例:当.dts  include .dtsi时,将i2c0status属性赋值为okay,则表明该主板上的i2c_bus0处于enable状态;反之,status赋值为disabled,则表明该主板上的i2c_bus0处于disenable状态。

    gic: interrupt-controller@10300000 {

        compatible = "arm,cortex-a7-gic";

        #interrupt-cells = <3>;

        #address-cells = <0>;

        interrupt-controller;

        /* gic dist base, gic cpu base , no virtual support */

        reg = <0x10301000 0x1000>, <0x10302000 0x1000>;

};

device tree的基本单元是node。这些node被组织成树状结构,除了root node,每个node都只有一个parent。一个device tree文件中只能有一个root node。每个node中包含了若干的property/value来描述该node的一些特性。每个node用节点名字(node name)标识,节点名字的格式是node-name@unit-address。如果该node没有reg属性(后面会描述这个property),那么该节点名字中必须不能包括@unit-addressunit-address的具体格式是和设备挂在那个bus上相关。示例对于cpu,其unit-address就是从0开始编址。而具体的设备,示例以太网控制器,其unit-address就是寄存器地址。root nodenode name是确定的,必须是“/”。

-----------------------------------------------------------------------------------

GICGeneric Interrupt Controller)是ARM公司提供的一个通用的中断控制器。GIC通过AMBAAdvanced Microcontroller Bus Architecture)这样的片上总线连接到一个或者多个ARM processor上。我们可以参考内核中自带的文档,用来参考:F:驱动开发hi3519v101kernellinux-3.181.ylinux-3.18.yDocumentationdevicetreebindingsinterrupt-controllerinterrupts.txt

一、设备树中中断如何工作

       与遵循树的自然结构而进行的地址转换不同,机器上的任何设备都可以发起和终止中断信号。另外地址的编址也不同于中断信号,前者是设备树的自然表示,而后者者表现为独立于设备树结构的节点之间的链接。描述中断连接需要四个属性:

   interrupt-controller - 一个空的属性定义该节点作为一个接收中断信号的设备。

   #interrupt-cells - 这是一个中断控制器节点的属性。它声明了该中断控制器的中断指示符中 cell 的个数(类似于 #address-cells #size-cells)。

   interrupt-parent

- 这是一个设备节点的属性,包含一个指向该设备连接的中断控制器的 phandle。那些没有 interrupt-parent 的节点则从它们的父节点中继承该属性。

   interrupts - 一个设备节点属性,包含一个中断指示符的列表,对应于该设备上的每个中断输出信号。

      中断指示符是一个或多个 cell 的数据(由 #interrupt-cells 指定),这些数据指定了该设备连接至哪些输入中断。在以下的例子中,大部分设备都只有一个输出中断,但也有可能在一个设备上有多个输出中断。一个中断指示符的意义完全取决于与中断控制器设备的 binding。每个中断控制器可以决定使用几个 cell 来唯一的定义一个输入中断。

需要注意的事情:

这个机器只有一个中断控制器:interrupt-controller@10300000

中断控制器节点上添加了‘gic:’标签,该标签用于给根节点的 interrupt-parent 属性分配一个 phandle。这个 interrupt-parent 将成为本系统的默认值,因为所有的子节点都将继承它,除非显示覆写这个属性。

每个设备使用 interrupts 属性来不同的中断输入线。

#interrupt-cells 2,所以每个中断指示符都有 2 cell。本例使用一种通用的模式,也就是用第一个 cell 来编码中断线号;然后用第二个 cell 编码标志位,比如高电平/低电平有效,或者边缘/水平触发。对于任何给定的中断控制器,请参考该控制器的binding 文档以了解指示符如何编码。

utm_source=copy

b) two cells

  The #interrupt-cells property is set to 2 and the first cell defines the index of the interrupt within the controller, while the second cell is used to specify any of the following flags:

- bits[3:0] trigger type and level flags

1 = low-to-high edge triggered

2 = high-to-low edge triggered

4 = active high level-sensitive

8 = active low level-sensitive

二、 GIC DTS描述

1、中断系统概述

    对于中断系统,主要有三个角色:

1processor:主要用于处理中断;

2Interrupt Generating Device:通过硬件的interrupt line表明自身需要处理器的进一步处理(示例有数据到来、异常状态等)

3interrupt controller:负责收集各个外设的异步事件,用有序、可控的方式通知一个或者多个processor

2DTS如何描述Interrupt Generating Device

     对于Interrupt Generating Device,我们需要定义下面两个属性:

1 Interrupt属性:该属性主要描述了中断的HW interrupt ID以及类型。

2interrupt-parent 属性:该属性主要描述了该设备的interrupt request line连接到哪一个interrupt controller

uart3: serial@48020000 {       

compatible = "ti,omap4-uart";     

 reg = <0x48020000 0x100="">;       

interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>;       

ti,hwmods = "uart3";       

clock-frequency = <48000000>;

};

对于uart3interrupts属性用3cell(对于device treecell是指由32bit组成的一个信息单位)表示。GIC_SPI 描述了interrupt type

对于GIC,它可以管理4种类型的中断:

1)外设中断(Peripheral interrupt

       根据目标CPU的不同,外设的中断可以分成PPIPrivate Peripheral Interrupt)和SPIShared Peripheral InterruptPPI只能分配给一个确定的processor,而SPI可以由Distributor将中断分配给一组Processor中的一个进行处理。外设类型的中断一般通过一个interrupt request line的硬件信号线连接到中断控制器,可能是电平触发的(Level-sensitive),也可能是边缘触发的(Edge-triggered)。

2)软件触发的中断(SGISoftware-generated interrupt

       软件可以通过写GICD_SGIR寄存器来触发一个中断事件,这样的中断,可以用于processor之间的通信。

3)虚拟中断(Virtual interrupt)和Maintenance interrupt

     这两种中断和本文无关,不再赘述。

     DTS中,外设的interrupt type有两种,一种是SPI,另外一种是PPISGI用于processor之间的通信,和外设无关。     uart3interrupt属性中的74表示该外设使用的GIC interrupt IDGIC最大支持1020HW interrupt ID,具体的ID分配情况如下:1ID0~ID31是用于分发到一个特定的processinterrupt。标识这些interrupt不能仅仅依靠ID,因为各个interrupt source都用同样的ID0~ID31来标识,因此识别这些interrupt需要interrupt ID CPU interface numberID0~ID15用于SGIID16~ID31用于PPIPPI类型的中断会送到指定的process上,和其他的process无关。SGI是通过写GICD_SGIR寄存器而触发的中断。Distributor通过processor source ID、中断IDtarget processor ID来唯一识别一个SGI2ID32~ID1019用于SPIuart3interrupt属性中的IRQ_TYPE_LEVEL_HIGH用来描述触发类型。

3DTS如何描述GIC

a -- compatible属性

      compatible属性用来描述GICprogramming model。该属性的值是string list,定义了一系列的modle(每个string是一个model)。这些字符串列表被操作系统用来选择用哪一个driver来驱动该设备。

     假设定义该属性:compatible = a厂商,p产品”, “标准bbb类型设备”。那么linux kernel可能首先使用“a厂商,p产品”来匹配适合的driver,如果没有匹配到,那么使用字符串“标准bbb类型设备”来继续寻找适合的driver

     compatible属性有两个应用场景:

1)对于root nodecompatible属性是用来匹配machine type的(参考Device Tree相关文档)

2)对于普通的HW block的节点,示例interrupt-controllercompatible属性是用来匹配适合的driver的。

b -- interrupt-controller 

       interrupt-controller这个没有定义value的属性用来表明本设备节点就是一个interrupt controller。理解#interrupt-cells这个属性需要理解interrupt specifierinterrupt domain这两个概念。interrupt specifier其实就是外设interrupt的属性值对于uart3而言,其interrupt specifier就是<GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,也就是说,interrupt specifier定义了一个外设产生中断的规格(HW interrupt ID interrupt type)。

     具体如何解析interrupt specifier?这个需要限定在一定的上下文中,不同的interrupt controller会有不同的解释。因此,对于一个包含多个interrupt controller的系统,每个interrupt controller及其相连的外设组成一个interrupt domain,各个外设的interrupt specifier只能在属于它的那个interrupt domain中得到解析。#interrupt-cells定义了在该interrupt domain中,用多少个cell来描述一个外设的interrupt

 specifier

c -- reg

      reg属性定义了GICmemory map的地址.

三、GICHW block diagram描述

1Distributor

      Distributor的主要的作用是检测各个interrupt source的状态,控制各个interrupt source的行为,分发各个interrupt source产生的中断事件到各个processorDistributor对中断的控制包括:

1)中断enable或者disable的控制。Distributor对中断的控制分成两个级别。一个是全局中断的控制。一旦disable了全局的中断,那么任何的interrupt source产生的interrupt event都不会被传递到CPU interface另外一个级别是对针对各个interrupt source进行控制disable某一个interrupt source会导致该interrupt event不会分发到CPU interface,但不影响其他interrupt source产生interrupt event的分发。

2)控制中断事件分发到process。一个interrupt事件可以分发给一个process,也可以分发给若干个process

3)优先级控制。

4interrupt属性设定。示例是level-sensitive还是edge-triggered,是属于group 0还是group 1

     Distributor可以管理若干个interrupt source,这些interrupt sourceID来标识,我们称之interrupt ID

2CPU interface

      CPU interface这个block主要用于和process进行接口。该block的主要功能包括:

1enable或者disable

     对于ARMCPU interface blockprocess之间的中断信号线是nIRQnFIQ这两个signal。如果disable了中断,那么即便是Distributor分发了一个中断事件到CPU interface,但是也不会assert指定的nIRQ或者nFIQ通知processor

2ackowledging中断

     processor会向CPU interface block应答中断,中断一旦被应答,Distributor就会把该中断的状态从pending状态修改成active。如果没有后续pending的中断,那么CPU interface就会deassert nIRQ或者nFIQsignal。如果在这个过程中又产生了新的中断,那么Distributor就会把该中断的状态从pending状态修改成pending and active。这时候,CPU interface仍然会保持nIRQ或者nFIQ信号的asserted状态,也就是向processor signal下一个中断。

3)中断处理完毕的通知

     interrupt handler处理完了一个中断的时候,会向写CPU interface的寄存器从而通知GIC CPU已经处理完该中断。做这个动作一方面是通知Distributor将中断状态修改为deactive,另外一方面,如果一个中断没有完成处理,那么后续比该中断优先级低的中断不会assertprocessor。一旦标记中断处理完成,被block掉的那些比当前优先级低的中断就会递交给processor

4)设定priority mask

     通过priority mask,可以mask掉一些优先级比较低的中断,这些中断不会通知到CPU

5)设定preemption的策略

6)在多个中断事件同时到来的时候,选择一个优先级最高的通知processor,下面我们随便分析一个。

interrupt-parent,就得首先讲讲Linux设备管理中对中断的设计思路演变。随着linux kernel的发展,在内核中将interrupt controller抽象成irqchip这个概念越来越流行,甚至GPIO controller也可以被看出一个interrupt controller chip,这样,系统中至少有两个中断控制器了,另外,在硬件上,随着系统复杂度加大,外设中断数据增加,实际上系统可以需要多个中断控制器进行级联,形成事实上的硬件中断处理结构:

https://www.veryarm.com/wp-content/uploads/images/201811/2018111723251229JZn.png

在这种趋势下,内核中原本的中断源直接到中断号的方式已经很难继续发展了,为了解决这些问题,linux kernel的大牛们就创造了irq domain(中断域)这个概念。domain在内核中有很多,除了irqdomain,还有power domainclock domain等等,所谓domain,就是领域,范围的意思,也就是说,任何的定义出了这个范围就没有意义了。如上所述,系统中所有的interrupt controller会形成树状结构,对于每个interrupt controller都可以连接若干个外设的中断请求(interrupt source,中断源),interrupt controller会对连接其上的interrupt source(根据其在Interrupt controller中物理特性)进行编号(也就是HW interrupt ID了)。有了irq domain这个概念之后,这个编号仅仅限制在本interrupt controller范围内,有了这样的设计,CPU(Linux 内核)就可以根据级联的规则一级一级的找到想要访问的中断。当然,通常我们关心的只是内核中的中断号,具体这个中断号是怎么找到相应的中断源的,我们作为程序员往往不需要关心,除了在写设备树的时候,设备树就是要描述嵌入式软件开发中涉及的所有硬件信息,所以,设备树就需要准确的描述硬件上处理中断的这种树状结构,如此,就有了我们的interrupt-parant这样的概念:用来连接这样的树状结构的上下级,用于表示这个中断归属于哪个interrupt controller

-------------------------------------------------------------------------------------------

    clock: clock0 {

        compatible = "hisilicon,hi3519v101-clock";

        #address-cells = <1>;

        #size-cells = <1>;

        #clock-cells = <1>;

        #reset-cells = <2>;

        reg = <0x12010000 0x10000>;

    };

CRGclock reset generate时钟复位发生器,使用CRG的技术可以利用时钟和CRG模块产生核心时钟和外设时钟。

    syscounter {

        compatible = "arm,armv7-timer";//名字,第一个是厂商       

        interrupt-parent = <&gic>;   //gic规范

        interrupts = <1 13 0xf08>,<1 14 0xf08>; //使用两个中断

        clock-frequency = <24000000>;    //系统时钟24M

    };

    soc {

        #address-cells = <1>;

        #size-cells = <1>; //一个地址,每个地址有一个大小描述

        compatible = "simple-bus";   //bus名称

        interrupt-parent = <&gic>;   //继承gic规范

        ranges; //ranges属性为空

ranges属性为地址转换表,这在pcie中使用较为常见,它表明了该设备在到parent节点中所对用的地址映射关系。ranges格式长度受当前节点#address-cellparent节点#address-cells、当前节点#size-cell所控制。顺序为ranges=<前节点#address-cell, parent节点#address-cells , 当前节点#size-cell。在本例中,当前节点#address-cell=<1>,对应于⑤中的第一个0x20000000parent节点#address-cells=<1>,对应于⑤中的第二个0x20000000;当前节点#size-cell=<1>,对应于⑤中的0x30000000。即ahb0节点所占空间从0x20000000地址开始,对应于父节点的0x20000000地址开始的0x30000000地址空间大小。

注:对于相同名称的节点,dtc会根据定义的先后顺序进行合并,其相同属性,取后定义的那个

        amba {

            #address-cells = <1>;

            #size-cells = <1>;

            compatible = "arm,amba-bus";

            ranges;

 

            uart0: uart@12100000 {

                compatible = "arm,pl011", "arm,primecell";

                reg = <0x12100000 0x1000>; //对应海思的空间映射表

                interrupts = <0 4 4>;

                clocks = <&clock HI3519_UART0_CLK>;  // 时钟在头文件中

                clock-names = "apb_pclk";  //猜测是使用的apb_pclk时钟

                status = "disabled";

            };

------------------

我们看下中断,interrupt-parent = <&gic>父节点gic规范下的interrupt-controller。父节点的address-cells = <3>;size-cells = <0>;我们看下gic规范。

interrupts = <0 4 4>;指的是使用SPI中断类型,第一个4指中断线,第二个4是指高电平触发中断。

------------------

。。。。。。。。。。。。。       

};

 

};

3. hisi-hi3519v101-hmp-demb.dts

如果说hisi-hi3519v101.dtsi中是一些公共资源,一般情况下我们不需要更改。那么hisi-hi3519v101-hmp-demb.dts中就是我们板载的一些资源,需要我们注意和修改的地方。

/dts-v1/;

#include "hisi-hi3519v101.dtsi"

/ {

    model = "Hisilicon HI3519V101 DEMO Board";

    compatible = "hisilicon,hi3519v101";

 

    cpus {

        #address-cells = <1>;

        #size-cells = <0>;

        enable-method = "hisilicon,hi3519-smp";

 

        cpu@0 {

            compatible = "arm,cortex-a7";

            device_type = "cpu";

            clock-frequency = <HI3519_FIXED_792M>;

            reg = <0>;

            cci-control-port = <&cci_control0>;

        };

 

        /*cpu@100 {

            compatible = "arm,cortex-a17";

            device_type = "cpu";

            clock-frequency = <HI3519_FIXED_1000M>;

            reg = <0x100>;

            cci-control-port = <&cci_control1>;

        };*/

    };

两个CPU。一个是A7,主频792M。一个是A17,主频是1000M

    memory {

        device_type = "memory";

        reg = <0x80000000 0x40000000>;   //DDR的地址空间:1G

    };

};

 

&uart0 {

    status = "okay";  //启用uart0

};

。。。。。。。。。。。。

&spi_bus0 {

    status = "okay";

 

    spidev@0 {

        compatible = "rohm,dh2228fv";    //这个名字比较奇怪,但不能改,不然就找不到spidev

        reg = <0>;

        pl022,interface = <0>;

        pl022,com_mode = <0>;

        spi-max-frequency = <24000000>;

    };

 

};

。。。。。。。。。。。。。。。