HI3519V101的设备树dts分析
1.目录分析
/hisilicon/hi3519v101/hi3519v101/osdrv/opensource/kernel/linux-3.18.y/arch/arm/boot/dts
我们看到有四个文件。在linux驱动开发编程模块中我们知道:
.dts文件是一种ASCII 文本格式的Device Tree描述,此文本格式非常人性化,适合人类的阅读习惯。基本上,在ARM Linux中,一个.dts文件对应一个ARM的machine,一般放置在内核的arch/arm/boot/dts/目录。由于一个SoC可能对应多个machine(一个SoC可以对应多个产品和电路板),势必这些.dts文件需包含许多共同的部分,Linux内核为了简化,把SoC公用的部分或者多个machine共同的部分一般提炼为.dtsi,类似于C语言的头文件。其他的machine对应的.dts就include这个.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 node。dtc会进行合并操作这一点从属性上也可以得到验证。这个稍后做讲解。
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决定了子节点的address和size的长度; 我们后面再详细探究这两个属性的含义。下面我们看下代码中的实现:
#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时,将i2c0的status属性赋值为okay,则表明该主板上的i2c_bus0处于enable状态;反之,status赋值为disabled,则表明该主板上的i2c_bus0处于disenable状态。
gic: [email protected] {
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)标识,节点名字的格式是[email protected]。如果该node没有reg属性(后面会描述这个property),那么该节点名字中必须不能包括@和unit-address。unit-address的具体格式是和设备挂在那个bus上相关。例如对于cpu,其unit-address就是从0开始编址。而具体的设备,例如以太网控制器,其unit-address就是寄存器地址。root node的node name是确定的,必须是“/”。
-----------------------------------------------------------------------------------
GIC(Generic Interrupt Controller)是ARM公司提供的一个通用的中断控制器。GIC通过AMBA(Advanced Microcontroller Bus Architecture)这样的片上总线连接到一个或者多个ARM processor上。我们可以参考内核中自带的文档,用来参考:F:\驱动开发\hi3519v101\kernel\linux-3.181.y\linux-3.18.y\Documentation\devicetree\bindings\interrupt-controller\interrupts.txt。
一、设备树中中断如何工作
与遵循树的自然结构而进行的地址转换不同,机器上的任何设备都可以发起和终止中断信号。另外地址的编址也不同于中断信号,前者是设备树的自然表示,而后者者表现为独立于设备树结构的节点之间的链接。描述中断连接需要四个属性:
■ interrupt-controller - 一个空的属性定义该节点作为一个接收中断信号的设备。
■ #interrupt-cells - 这是一个中断控制器节点的属性。它声明了该中断控制器的中断指示符中 cell 的个数(类似于 #address-cells 和 #size-cells)。
■ interrupt-parent
- 这是一个设备节点的属性,包含一个指向该设备连接的中断控制器的 phandle。那些没有 interrupt-parent 的节点则从它们的父节点中继承该属性。
■ interrupts - 一个设备节点属性,包含一个中断指示符的列表,对应于该设备上的每个中断输出信号。
中断指示符是一个或多个 cell 的数据(由 #interrupt-cells 指定),这些数据指定了该设备连接至哪些输入中断。在以下的例子中,大部分设备都只有一个输出中断,但也有可能在一个设备上有多个输出中断。一个中断指示符的意义完全取决于与中断控制器设备的 binding。每个中断控制器可以决定使用几个 cell 来唯一的定义一个输入中断。
需要注意的事情:
■ 这个机器只有一个中断控制器:int[email protected]。
■ 中断控制器节点上添加了‘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、中断系统概述
对于中断系统,主要有三个角色:
(1)processor:主要用于处理中断;
(2)Interrupt Generating Device:通过硬件的interrupt line表明自身需要处理器的进一步处理(例如有数据到来、异常状态等)
(3)interrupt controller:负责收集各个外设的异步事件,用有序、可控的方式通知一个或者多个processor。
2、DTS如何描述Interrupt Generating Device
对于Interrupt Generating Device,我们需要定义下面两个属性:
(1) Interrupt属性:该属性主要描述了中断的HW interrupt ID以及类型。
(2)interrupt-parent 属性:该属性主要描述了该设备的interrupt request line连接到哪一个interrupt controller。
uart3: [email protected] {
compatible = "ti,omap4-uart";
reg = <0x48020000 0x100="">;
interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>;
ti,hwmods = "uart3";
clock-frequency = <48000000>;
};
对于uart3,interrupts属性用3个cell(对于device tree,cell是指由32bit组成的一个信息单位)表示。GIC_SPI 描述了interrupt type。
对于GIC,它可以管理4种类型的中断:
1)外设中断(Peripheral interrupt)
根据目标CPU的不同,外设的中断可以分成PPI(Private Peripheral Interrupt)和SPI(Shared Peripheral Interrupt)。PPI只能分配给一个确定的processor,而SPI可以由Distributor将中断分配给一组Processor中的一个进行处理。外设类型的中断一般通过一个interrupt request line的硬件信号线连接到中断控制器,可能是电平触发的(Level-sensitive),也可能是边缘触发的(Edge-triggered)。
2)软件触发的中断(SGI,Software-generated interrupt)
软件可以通过写GICD_SGIR寄存器来触发一个中断事件,这样的中断,可以用于processor之间的通信。
3)虚拟中断(Virtual interrupt)和Maintenance interrupt。
这两种中断和本文无关,不再赘述。
在DTS中,外设的interrupt type有两种,一种是SPI,另外一种是PPI。SGI用于processor之间的通信,和外设无关。 uart3的interrupt属性中的74表示该外设使用的GIC interrupt ID号。GIC最大支持1020个HW interrupt ID,具体的ID分配情况如下:1)ID0~ID31是用于分发到一个特定的process的interrupt。标识这些interrupt不能仅仅依靠ID,因为各个interrupt source都用同样的ID0~ID31来标识,因此识别这些interrupt需要interrupt ID + CPU interface number。ID0~ID15用于SGI,ID16~ID31用于PPI。PPI类型的中断会送到指定的process上,和其他的process无关。SGI是通过写GICD_SGIR寄存器而触发的中断。Distributor通过processor source ID、中断ID和target processor ID来唯一识别一个SGI。2)ID32~ID1019用于SPI。uart3的interrupt属性中的IRQ_TYPE_LEVEL_HIGH用来描述触发类型。
3、DTS如何描述GIC
a -- compatible属性
compatible属性用来描述GIC的programming model。该属性的值是string list,定义了一系列的modle(每个string是一个model)。这些字符串列表被操作系统用来选择用哪一个driver来驱动该设备。
假设定义该属性:compatible = “a厂商,p产品”, “标准bbb类型设备”。那么linux kernel可能首先使用“a厂商,p产品”来匹配适合的driver,如果没有匹配到,那么使用字符串“标准bbb类型设备”来继续寻找适合的driver。
compatible属性有两个应用场景:
1)对于root node,compatible属性是用来匹配machine type的(参考Device Tree相关文档)
2)对于普通的HW block的节点,例如interrupt-controller,compatible属性是用来匹配适合的driver的。
b -- interrupt-controller
interrupt-controller这个没有定义value的属性用来表明本设备节点就是一个interrupt controller。理解#interrupt-cells这个属性需要理解interrupt specifier和interrupt 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属性定义了GIC的memory map的地址.
三、GIC的HW block diagram描述
1、Distributor
Distributor的主要的作用是检测各个interrupt source的状态,控制各个interrupt source的行为,分发各个interrupt source产生的中断事件到各个processor。Distributor对中断的控制包括:
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)优先级控制。
4)interrupt属性设定。例如是level-sensitive还是edge-triggered,是属于group 0还是group 1。
Distributor可以管理若干个interrupt source,这些interrupt source用ID来标识,我们称之interrupt ID。
2、CPU interface
CPU interface这个block主要用于和process进行接口。该block的主要功能包括:
1)enable或者disable
对于ARM,CPU interface block和process之间的中断信号线是nIRQ和nFIQ这两个signal。如果disable了中断,那么即便是Distributor分发了一个中断事件到CPU interface,但是也不会assert指定的nIRQ或者nFIQ通知processor。
2)ackowledging中断
processor会向CPU interface block应答中断,中断一旦被应答,Distributor就会把该中断的状态从pending状态修改成active。如果没有后续pending的中断,那么CPU interface就会deassert nIRQ或者nFIQ的signal。如果在这个过程中又产生了新的中断,那么Distributor就会把该中断的状态从pending状态修改成pending and active。这时候,CPU interface仍然会保持nIRQ或者nFIQ信号的asserted状态,也就是向processor signal下一个中断。
3)中断处理完毕的通知
当interrupt handler处理完了一个中断的时候,会向写CPU interface的寄存器从而通知GIC CPU已经处理完该中断。做这个动作一方面是通知Distributor将中断状态修改为deactive,另外一方面,如果一个中断没有完成处理,那么后续比该中断优先级低的中断不会assert到processor。一旦标记中断处理完成,被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,这样,系统中至少有两个中断控制器了,另外,在硬件上,随着系统复杂度加大,外设中断数据增加,实际上系统可以需要多个中断控制器进行级联,形成事实上的硬件中断处理结构:
在这种趋势下,内核中原本的中断源直接到中断号的方式已经很难继续发展了,为了解决这些问题,linux kernel的大牛们就创造了irq domain(中断域)这个概念。domain在内核中有很多,除了irqdomain,还有power domain,clock 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>;
};
CRG是clock 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-cell、parent节点#address-cells、当前节点#size-cell所控制。顺序为ranges=<前节点#address-cell, parent节点#address-cells , 当前节点#size-cell。在本例中,当前节点#address-cell=<1>,对应于⑤中的第一个0x20000000;parent节点#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: [email protected] {
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";
compatible = "arm,cortex-a7";
device_type = "cpu";
clock-frequency = <HI3519_FIXED_792M>;
reg = <0>;
cci-control-port = <&cci_control0>;
};
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";
compatible = "rohm,dh2228fv"; //这个名字比较奇怪,但不能改,不然就找不到spidev
reg = <0>;
pl022,interface = <0>;
pl022,com_mode = <0>;
spi-max-frequency = <24000000>;
};
};
。。。。。。。。。。。。。。。