设备树DTS 学习:2 您所在的位置:网站首页 设备节点文件有哪些内容 设备树DTS 学习:2

设备树DTS 学习:2

2024-07-02 03:12| 来源: 网络整理| 查看: 265

背景

通过上一讲了解完设备树DTS有关概念,我们这一讲就来基于设备树例程,学习设备树的语法规则。

参考:设备树详解dts、设备树语法详解、设备树使用总结

设备树框架

1个dts文件 + n个dtsi文件,它们编译而成的dtb文件就是真正的设备树。

基于同样的软件分层设计的思想,由于一个SoC可能对应多个machine,如果每个machine的设备树都写成一个完全独立的.dts文件,那么势必相当一些.dts文件有重复的部分。 为了解决这个问题,Linux设备树目录把一个SoC公用的部分或者多个machine共同的部分提炼为相应的.dtsi文件。 这样每个.dts就只有自己差异的部分,公有的部分只需要"include"相应的.dtsi文件, 以保证整个设备树的管理更加有序。 以solidrun公司的hummingboard为例,其组成为

imx6dl-hummingboard.dts |_imx6dl.dtsi | |_imx6qdl.dtsi |_imx6qdl-microsom.dtsi |_imx6qdl-microsom-ar8035.dtsi

此外,dts/dtsi兼容c语言的一些语法,能使用宏定义,也能包含.h文件

设备树用树状结构描述设备信息,它有以下几种特性:

每个设备树文件都有一个根节点,每个设备都是一个节点。 节点由 节点名 + 属性 组成。 节点间可以嵌套,形成父子关系,这样就可以方便的描述设备间的关系。 每个设备的属性都用一组key-value对(键值对)来描述。 每个属性的描述用;结束

所以,一个设备树的基本框架可以写成下面这个样子,一般来说,/表示板子,它的子节点node1表示SoC上的某个控制器,控制器中的子节点node2表示挂接在这个控制器上的设备(们)。

/ { //根节点 node1{ //node1是节点名,是/的子节点 key=value; //node1的属性 ... node2{ //node2是node1的子节点 key=value; //node2的属性 ... } } //node1的描述到此为止 node3{ key=value; ... } }

以下是一颗最简单的设备树: 注意/dts-v1/;是必须的,有时候正是因为忽略了它而引起了syntax error且没有其他提示。

/dts-v1/; / { }; 节点node

{}包围起来的结构称之为节点,dts中最开头的/ {},称为根节点。 在节点中,以 key = value 代表节点属性。 树中每个表示一个设备的节点都需要一个 compatible 属性。

节点名 name 节点名称:每个节点名格式为:[@],其中: :设备名,就是一个不超过31位的简单 ascii 字符串,节点的命名应该根据它所体现的是什么样的设备。 :设备地址,用来唯一标识一共节点。没有指定时,同级节点命名必须是唯一的;但只要不同,多个节点也可以使用一样的通用名称。

下面是典型节点名的写法:

/ { model = "Freescale i.MX23 Evaluation Kit"; compatible = "fsl,imx23-evk", "fsl,imx23"; memory { reg = ; }; // 注意这里 apb@80000000 { ... }; }

上面的节点名是apb,节点路径是/apb@80000000 ,这点要注意,因为根据节点名查找节点的API的参数是不能有"@xxx"这部分的。

Linux中的设备树还包括几个特殊的节点:比如chosen,chosen节点不描述一个真实设备,而是用于firmware传递一些数据给OS,比如bootloader传递内核启动参数给内核

/include/ "zynq-7000.dtsi" / { model = "Zynq ZC702 Development Board"; compatible = "xlnx,zynq-zc702", "xlnx,zynq-7000"; ... chosen { bootargs = "console=ttyPS1,115200 earlyprintk"; }; };

引用

当我们找一个节点的时候,我们必须书写完整的节点路径,这样当一个节点嵌套比较深的时候就不是很方便。所以,设备树允许我们用下面的形式为节点标注引用(起别名),借以省去冗长的路径。 标号引用常常还作为节点的重写方式,用于修改节点属性。

格式: 声明别名: 别名 : 节点名 访问 : &别名

编译设备树的时候,相同的节点的不同属性信息都会被合并,相同节点的相同的属性会被重写(覆盖前值),使用引用可以避免移植者四处找节点,直接在板级.dts增改即可。

/include/ "imx53.dtsi" / { model = "Freescale i.MX53 Automotive Reference Design Board"; compatible = "fsl,imx53-ard", "fsl,imx53"; memory { reg = ; }; eim-cs1@f4000000 { #address-cells = ; #size-cells = ; compatible = "fsl,eim-bus", "simple-bus"; reg = ; lan9220@f4000000 { compatible = "smsc,lan9220", "smsc,lan9115"; reg = ; phy-mode = "mii"; interrupt-parent = ; // 直接使用引用 vdd33a-supply = ; }; }; regulators { compatible = "simple-bus"; reg_3p3v: 3p3v { // 定义一个引用 compatible = "regulator-fixed"; regulator-name = "3P3V"; }; }; ... // 引用一个节点,新增/修改其属性。 ®_3p3v { regulator-always-on; } 节点属性 property

属性一般由 key = value; 键值对构成。 Linux设备树语法中定义了一些具有规范意义的属性,包括:compatible, address, interrupt等,这些信息能够在内核初始化找到节点的时候,自动解析生成相应的设备信息。 此外,还有一些Linux内核定义好的,一类设备通用的有默认意义的属性,这些属性一般不能被内核自动解析生成相应的设备信息,但是内核已经编写的相应的解析提取函数,常见的有 "mac_addr","gpio","clock","power"。"regulator" 等等。

简单的键-值对,它的值可以为空或者包含一个任意字节流。虽然数据类型并没有编码进数据结构,但在设备树源文件中任有几个基本的数据表示形式: 文本字符串(无结束符)可以用双引号表示: string-property = "a string" Cells是 32 位无符号整数,用尖括号限定: cell-property = 二进制数据用方括号限定: binary-property = [01 23 45 67]; 不同表示形式的数据可以使用逗号连在一起: mixed-property = "a string", [01 23 45 67], ; 逗号也可用于创建字符串列表: string-list = "red fish", "blue fish"; 混合形式:上述几种的混合形式 compatible 兼容性

如果一个节点是设备节点,那么它一定要有compatible(兼容性),因为这将作为驱动和设备(设备节点)的匹配依据,compatible(兼容性)的值可以有不止一个字符串以满足不同的需求。(设备节点中对应的节点信息已经被内核构造成struct platform_device。驱动可以通过相应的函数从中提取信息。) compatible属性是用来查找节点的方法之一,另外还可以通过节点名或节点路径查找指定节点。 而根节点的compatible也是非常重要的,一般在系统启动以后,用于识别对应系统一些东西,并由此进行对应的初始化。

格式:compatible = "," [, ","] manufacturer指定厂家名,model指定特定设备型号;后续的指定兼容的设备型号(其中,后续的 可空,第二个model也可空)。 我们来看 compatible 是如何与 驱动捆绑在一起的: 可以看出,驱动中用于匹配的结构使用的compatible和设备树中一模一样,否则就可能无法匹配,这里另外的一点是struct of_device_id数组的最后一个成员一定是空,因为相关的操作API会读取这个数组直到遇到一个空。

1)先随便在设备树中出一个网卡设备,关键是找到 compatible 属性中的 值。

// 文件节选于:arch/arm/boot/dts/vexpress-v2m-rs1.dtsi ethernet@2,02000000 { compatible = "smsc,lan9118", "smsc,lan9115"; reg = ; interrupts = ; phy-mode = "mii"; reg-io-width = ; smsc,irq-active-high; smsc,irq-push-pull; vdd33a-supply = ; vddvario-supply = ; };

2)在驱动中(为了方便读者理解,这里在内核源码根目录下查找,实际上就是在driver目录中),找到对应的.compatible 关键字所在的文件以及行数。

$ find . 2>/dev/null | grep lan9115 arch/arm/boot/dts/vexpress-v2m-rs1.dtsi:50: compatible = "smsc,lan9118", "smsc,lan9115"; arch/arm/boot/dts/vexpress-v2m.dtsi:49: compatible = "smsc,lan9118", "smsc,lan9115"; drivers/net/ethernet/smsc/smsc911x.c:2578: { .compatible = "smsc,lan9115", },

3)顺藤摸瓜,找到所在行,也就找到了用来描述设备信息的结构体of_device_id 。

// 节选于 drivers/net/ethernet/smsc/smsc911x.c #ifdef CONFIG_OF static const struct of_device_id smsc911x_dt_ids[] = { { .compatible = "smsc,lan9115", }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, smsc911x_dt_ids); #endif

可以看出,驱动中用于匹配的结构使用的compatible和设备树中一模一样,且,字符串需要严格匹配。

注:这里另外的一点是 struct of_device_id 数组的最后一个成员一定是空,因为相关的操作API会读取这个数组直到遇到一个空。 i2c和spi驱动还支持一种“别名匹配”的机制,就以pcf8523为例,假设某程序员在设备树中的pcf8523设备节点中写了compatible = "pcf8523";,显然相对于驱动id_table中的"nxp,pcf8523",他遗漏了nxp字段,但是驱动却仍然可以匹配上,因为别名匹配对compatible中字符串里第二个字段敏感。 驱动程序将直接和设备树里的设备节点进行配对,是通过设备节点中的compatible(兼容性)来与设备节点进行配对的,具体的应用详见 基于i2c子系统的驱动分析、 基于platform总线的驱动分析

address 地址属性

有关节点的地址,比如i2c@021a0000,虽然它在名字后面跟了地址,但是正式的设置是在reg属性中设置。 (几乎)所有的设备都需要与CPU的IO口相连,所以其IO端口信息就需要在设备节点节点中说明。常用的属性有:

#address-cells = ,用来描述子节点"reg"属性的地址表中用来描述首地址的cell的数量 #size-cells = , 用来描述子节点"reg"属性的地址表中用来描述地址长度的cell的数量。 reg = : address 代表基地址, length 代表长度。基址和长度的格式是可变的,addr由父节点的#address-cells个uint32值组成,len由父节点的#size-cells个uint32值组成。表明了设备使用的一个地址范围。 例如: aips-bus@02000000 { /* AIPS1 */ compatible = "fsl,aips-bus", "simple-bus"; #address-cells = ; #size-cells = ; reg = ; i2c1: i2c@021a0000 { #address-cells = ; #size-cells = ; compatible = "fsl,imx6q-i2c", "fsl,imx21-i2c"; reg = ; rtc: rtc@68 { compatible = "stm,mt41t62"; reg = ; }; }; }; /* 我们知道,aips-bus@02000000 是 i2c@021a0000 的父节点;i2c@021a0000 是 rtc@68 的父节点。 aips-bus@02000000的 #address-cells 和#size-cells均为1,所以 i2c@021a0000 中的 `reg` 格式为: `` i2c@021a0000的 #address-cells 和#size-cells分别为1和0,所以 rtc@68 中的 `reg` 格式为: `` 通俗来讲,如果现在有 一个节点A的 #address-cells 和#size-cells分别为2和1;那么A的子节点B 的 `reg`格式为 `` */ interrupts 中断属性

中断产生设备用interrupts属性描述中断源(interrupt specifier),因为不同的硬件描述中断源需要的数据量不同,所以interrupts属性的类型也是。为了明确表示一个中断由几个u32表示,又引入了#interrupt-cells属性,#interrupt-cells属性的类型是u32,假如一个中断源需要2个u32表示(一个表示中断号,另一个表示中断类型),那么#interrupt-cells就设置成2。 有些情况下,设备树的父节点不是中断的父节点(主要是中断控制器一般不是父节点),为此引入了interrupt-parent属性,该属性的类型是,用来引用中断父节点(我们前边说过,一般用父节点的标签,这个地方说中断父节点而不是中断控制器是有原因的)。如果设备树的父节点就是中断父节点,那么可以不用设置interrupt-parent属性。interrupts属性和interrupt-parent属性都是中断产生设备节点的属性,但是#interrupt-cells属性不是,#interrupt-cells属性是中断控制器节点以及interrupt nexus节点的属性,这两类节点都可能是中断父节点。

一个计算机系统中大量设备都是通过中断请求CPU服务的,所以设备节点中就需要在指定中断号。常用的属性有:

interrupt-controller: 一个空属性用来声明这个node接收中断信号,即这个node是一个中断控制器。 #interrupt-cells :是中断控制器节点的属性,用来标识这个控制器需要几个单位做中断描述符,用来描述子节点中interrupts属性使用了父节点中的interrupts属性的具体的哪个值。一般,如果父节点的该属性的值是3,则子节点的interrupts一个cell的三个32bits整数值分别为:,如果父节点的该属性是2,则是 interrupt-parent:标识此设备节点属于哪一个中断控制器,如果没有设置这个属性,会自动依附父节点的 interrupts:一个中断标识符列表,表示每一个中断输出信号。 reg : 在schips todo

这里重点说明一下,interrupts 属性,在ARM GIC(Generic Interrupt Controller)中:

备注:ARM GIC 说明文档位于:Documentation/devicetree/bindings/arm/gic.txt ;此外,本人并没有找到 #interrupt-cells为1个时的文档说明。

当interrupt-cells为3时,interrupts包含三个cells,如interrupts = :

第一个cell代表中断类型:0 表示SPI中断,1 表示PPI中断。

第二个cell代表具体的中断类型:、

PPI中断:私有外设中断(Private Peripheral Interrupt),是每个CPU私有的中断。最多支持16个PPI中断,范围【0 - 15】。 SPI中断类型:公用外设中断(Shared Peripheral Interrupt),最多可以支持988个外设中断,范围【0 - 987】。

第三个cell代表中断触发标志:

bits [ 3 :0 ] 触发类型和级别标志: 1 = 低- 至- 高边沿触发 2 = 高- 到- 低边沿触发 4 = 活跃的高水平 - 敏感 8 = 低电平有效 - 敏感 bits [ 15 :8 ] PPI中断cpu掩码。每个位对应于每个位附加到GIC的8个可能的cpu。指示设置为"1"的位中断被连接到该CPU 。只有有效的PPI中断。

当interrupt-cells为2时,interrupts包含2个cells,如interrupts = :

第一个cell代表具体的中断类型:

SGI中断:软件触发中断(Software Generated Interrupt),通常用于多核间通讯,最多支持16个SGI中断,硬件中断号从ID0~ID15。 PPI中断:私有外设中断(Private Peripheral Interrupt),是每个CPU私有的中断。最多支持16个PPI中断,硬件中断号从ID16~ID31。 SPI中断类型:公用外设中断(Shared Peripheral Interrupt),最多可以支持988个外设中断,硬件中断号从ID32~ID1019。

第二个cell代表中断触发标志:

bits [ 3 :0 ] 触发类型和级别标志:

1 = 低- 至- 高边沿触发

2 = 高- 到- 低边沿触发

4 = 活跃的高水平- 敏感

8 = 低电平有效- 敏感

bits [ 15 :8 ] PPI中断cpu掩码。每个位对应于每个位附加到GIC的8个可能的cpu。指示设置为"1"的位中断被连接到该CPU 。只有有效的PPI中断。

/ { compatible = "acme,coyotes-revenge"; #address-cells = ; #size-cells = ; interrupt-parent = ;//指定依附的中断控制器是intc serial@101f0000 { //子节点:串口设备 compatible = "arm,pl011"; reg = ; interrupts = < 1 0 >; }; intc: interrupt-controller@10140000 { //intc中断控制器 compatible = "arm,pl190"; reg = ; interrupt-controller;//定义为中断控制器设备 #interrupt-cells = ; }; } GPIO 属性

gpio也是最常见的IO口,常用的属性有:

"gpio-controller",用来说明该节点描述的是一个gpio控制器 "#gpio-cells",用来描述gpio使用节点的属性一个cell的内容,即 `属性 =

通过上面的属性定义以后,就可以使用它,例如:

2 &spi_1 { 1 status = "okay"; 388 cs-gpios = ; // 使用 GPIO A2 第5个引脚, 1 2 w25q80bw@0 { 3 #address-cells = ; 4 #size-cells = ; 5 compatible = "w25x80"; 6 reg = ; 7 spi-max-frequency = ; 8 9 controller-data { 10 samsung,spi-feedback-delay = ; 11 }; 12 驱动自定义key属性

针对具体的设备,有部分属性很难做到通用,需要驱动自己定义好。 可以在设备树中自定义key属性,再在驱动中通过内核的属性提取解析函数进行值的获取。 比如:

/* 有关的 设备树写法 */ 6 ethernet@2,02000000 { 5 compatible = "smsc,lan9118", "smsc,lan9115"; 4 reg = ; 3 interrupts = ; 2 phy-mode = "mii"; 1 reg-io-width = ; 55 smsc,irq-active-high; // 自定义key 1 smsc,irq-push-pull; // 自定义key 2 vdd33a-supply = ; 3 vddvario-supply = ; 4 }; 5 6 usb@2,03000000 { 7 compatible = "nxp,usb-isp1761"; 8 reg = ; 9 interrupts = ; arch/arm/boot/dts/vexpress-v2m-rs1.dtsi /* 有关的驱动写法 */ 2389 if (of_get_property(np, "smsc,irq-active-high", NULL)) 1 config->irq_polarity = SMSC911X_IRQ_POLARITY_ACTIVE_HIGH; 2 3 if (of_get_property(np, "smsc,irq-push-pull", NULL)) 4 config->irq_type = SMSC911X_IRQ_TYPE_PUSH_PULL; 5 6 if (of_get_property(np, "smsc,force-internal-phy", NULL)) 7 config->flags |= SMSC911X_FORCE_INTERNAL_PHY; 8 9 if (of_get_property(np, "smsc,force-external-phy", NULL)) 10 config->flags |= SMSC911X_FORCE_EXTERNAL_PHY; 11 12 if (of_get_property(np, "smsc,save-mac-address", NULL)) 13 config->flags |= SMSC911X_SAVE_MAC_ADDRESS; 14 15 return 0; drivers/net/ethernet/smsc/smsc911x.c 附录:补充对于interrupt-parent的一些知识点

为什么会有interrupt-parent?

首先讲讲Linux设备管理中对中断的设计思路演变。随着linux kernel的发展,在内核中将interrupt controller抽象成irqchip这个概念越来越流行,甚至GPIO controller也可以被看出一个interrupt controller chip,这样,系统中至少有两个中断控制器了。另外,在硬件上,随着系统复杂度加大,外设中断数据增加,在这种趋势下,内核中原本的中断源直接到中断号的方式已经很难继续发展了,为了解决这些问题,linux kernel的大牛们就创造了irq domain(中断域)这个概念。domain在内核中有很多,除了irqdomain,还有power domain,clock 这些domain等等;所谓domain,就是领域,范围的意思(即:任何的定义出了这个范围就没有意义了)。

实际上系统可以需要多个中断控制器进行级联,形成事实上的硬件中断处理结构:

img

如上所述,系统中所有的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,比如,一个接在GPIO上的按键,它的组织形式就是:

中断源--interrupt parent-->GPIO--interrupt parent-->GIC1--interrupt parent-->GIC2--...-->CPU

有了parant,我们就可以使用一级一级的偏移量来最终获得当前中断的绝对编号。

可以看出,在我板子上的dm9000的的设备节点中,它的"interrupt-parent"引用了"exynos4x12-pinctrl.dtsi"(被板级设备树的exynos4412.dtsi包含)中的gpx0节点: img

而在gpx0节点中,指定了#interrupt-cells = ;,所以在dm9000中的属性interrupts = ;表示dm9000的的中断在作为irq parant的gpx0中的中断偏移量,即gpx0中的属性interrupts中的,通过查阅exynos4412的手册知道,对应的中断号是EINT[6]。

img



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有