Linux设备树语法详解【转】

转自:http://www.cnblogs.com/xiaojiang1025/p/6131381.htmlhtml

概念

Linux内核从3.x开始引入设备树的概念,用于实现驱动代码与设备信息相分离。在设备树出现之前,全部关于设备的具体信息都要写在驱动里,一旦外围设备变化,驱动代码就要重写。引入了设备树以后,驱动代码只负责处理驱动的逻辑,而关于设备的具体信息存放到设备树文件中,这样,若是只是硬件接口信息的变化而没有驱动逻辑的变化,驱动开发者只须要修改设备树文件信息,不须要改写驱动代码。好比在ARM Linux内,一个.dts(device tree source)文件对应一个ARM的machine,通常放置在内核的"arch/arm/boot/dts/"目录内,好比exynos4412参考板的板级设备树文件就是"arch/arm/boot/dts/exynos4412-origen.dts"。这个文件能够经过$make dtbs命令编译成二进制的.dtb文件供内核驱动使用。node

基于一样的软件分层设计的思想,因为一个SoC可能对应多个machine,若是每一个machine的设备树都写成一个彻底独立的.dts文件,那么势必至关一些.dts文件有重复的部分,为了解决这个问题,Linux设备树目录把一个SoC公用的部分或者多个machine共同的部分提炼为相应的.dtsi文件。这样每一个.dts就只有本身差别的部分,公有的部分只须要"include"相应的.dtsi文件, 这样就是整个设备树的管理更加有序。我这里用`Linux4.8.5源码自带的dm9000网卡为例来分析设备树的使用和移植。这个网卡的设备树节点信息在"Documentation/devicetree/bindings/net/davicom-dm9000.txt"有详细说明,其网卡驱动源码是"drivers/net/ethernet/davicom/dm9000.c"linux

设备树框架

设备树用树状结构描述设备信息,它有如下几种特性程序员

  1. 每一个设备树文件都有一个根节点,每一个设备都是一个节点。
  2. 节点间能够嵌套,造成父子关系,这样就能够方便的描述设备间的关系。
  3. 每一个设备的属性都用一组key-value对(键值对)来描述。
  4. 每一个属性的描述用;结束

因此,一个设备树的基本框架能够写成下面这个样子,通常来讲,/表示板子,它的子节点node1表示SoC上的某个控制器,控制器中的子节点node2表示挂接在这个控制器上的设备(们)数组

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

节点名

理论个节点名只要是长度不超过31个字符的ASCII字符串便可,此外
Linux内核还约定设备名应写成形如<name>[@<unit_address>]的形式,其中name就是设备名,最长能够是31个字符长度。unit_address通常是设备地址,用来惟一标识一个节点,下面就是典型节点名的写法markdown

上面的节点名是firmware,节点路径是/firmware@0203f000,这点要注意,由于根据节点名查找节点的API的参数是不能有"@xxx"这部分的框架

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

引用

当咱们找一个节点的时候,咱们必须书写完整的节点路径,这样当一个节点嵌套比较深的时候就不是很方便,因此,设备树容许咱们用下面的形式为节点标注引用(起别名),借以省去冗长的路径。这样就能够实现相似函数调用的效果。编译设备树的时候,相同的节点的不一样属性信息都会被合并,相同节点的相同的属性会被重写,使用引用能够避免移植者四处找节点,直接在板级.dts增改便可。
socket

下面的例子中就是直接引用了dtsi中的一个节点,并向其中添加/修改新的属性信息
函数

KEY

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

compatible

设备节点中对应的节点信息已经被内核构形成struct platform_device。驱动能够经过相应的函数从中提取信息。compatible属性是用来查找节点的方法之一,另外还能够经过节点名或节点路径查找指定节点。dm9000驱动中就是使用下面这个函数经过设备节点中的"compatible"属性提取相应的信息,因此两者的字符串须要严格匹配。
在下面的这个dm9000的例子中,咱们在相应的板级dts中找到了这样的代码块:

而后咱们取内核源码中找到dm9000的网卡驱动,从中能够发现这个驱动是使用的设备树描述的设备信息(这不废话么,显然用设备树好处多多)。咱们能够找到它用来描述设备信息的结构体,能够看出,驱动中用于匹配的结构使用的compatible和设备树中如出一辙,不然就可能没法匹配,这里另外的一点是struct of_device_id数组的最后一个成员必定是空,由于相关的操做API会读取这个数组直到遇到一个

address

(几乎)全部的设备都须要与CPU的IO口相连,因此其IO端口信息就须要在设备节点节点中说明。经常使用的属性有

  • #address-cells,用来描述子节点"reg"属性的地址表中用来描述首地址的cell的数量
  • #size-cells,用来描述子节点"reg"属性的地址表中用来描述地址长度的cell的数量

有了这两个属性,子节点中的"reg"就能够描述一块连续的地址区域。下例中,父节点中指定了#address-cells = <2>;#size-cells = <1>,则子节点dev-bootscs0中的reg中的前两个数表示一个地址,即MBUS_ID(0xf0, 0x01)0x1045C,最后一个数的表示地址跨度,便是0x4

interrupts

一个计算机系统中大量设备都是经过中断请求CPU服务的,因此设备节点中就须要在指定中断号。经常使用的属性有

  • interrupt-controller 一个空属性用来声明这个node接收中断信号,即这个node是一个中断控制器。
  • #interrupt-cells,是中断控制器节点的属性,用来标识这个控制器须要几个单位作中断描述符,用来描述子节点中"interrupts"属性使用了父节点中的interrupts属性的具体的哪一个值。通常,若是父节点的该属性的值是3,则子节点的interrupts一个cell的三个32bits整数值分别为:<中断域 中断 触发方式>,若是父节点的该属性是2,则是<中断 触发方式>
  • interrupt-parent,标识此设备节点属于哪个中断控制器,若是没有设置这个属性,会自动依附父节点的
  • interrupts,一个中断标识符列表,表示每个中断输出信号

设备树中中断的部分涉及的部分比较多,interrupt-controller表示这个节点是一个中断控制器,须要注意的是,一个SoC中可能有不止一个中断控制器,这就会涉及到设备树中断组织的不少概念,下面是在文件"arch/arm/boot/dts/exynos4.dtsi"中对exynos4412的中断控制器(GIC)节点描述:

要说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,好比,一个接在GPIO上的按键,它的组织形式就是:

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

有了parant,咱们就可使用一级一级的偏移量来最终得到当前中断的绝对编号,这里,能够看出,在我板子上的dm9000的的设备节点中,它的"interrupt-parent"引用了"exynos4x12-pinctrl.dtsi"(被板级设备树的exynos4412.dtsi包含)中的gpx0节点:

而在gpx0节点中,指定了"#interrupt-cells = <2>;",因此在dm9000中的属性"interrupts = <6 4>;"表示dm9000的的中断在做为irq parant的gpx0中的中断偏移量,即gpx0中的属性"interrupts"中的"<0 22 0>",经过查阅exynos4412的手册知道,对应的中断号是EINT[6]。

gpio

gpio也是最多见的IO口,经常使用的属性有

  • "gpio-controller",用来讲明该节点描述的是一个gpio控制器
  • "#gpio-cells",用来描述gpio使用节点的属性一个cell的内容,即 `属性 = <&引用GPIO节点别名 GPIO标号 工做模式>

GPIO的设置一样采用了上述偏移量的思想,好比下面的这个led的设备书,表示使用GPX2组的第7个引脚:

驱动自定义key

针对具体的设备,有部分属性很难作到通用,须要驱动本身定义好,经过内核的属性提取解析函数进行值的获取,好比dm9000节点中的下面这句就是自定义的节点属性,用以表示配置EEPROM不可用。

VALUE

dts描述一个键的值有多种方式,固然,一个键也能够没有值

字符串信息

32bit无符号整型数组信息

二进制数数组

字符串哈希表

混合形式

上述几种的混合形式

设备树/驱动移植实例

设备树就是为驱动服务的,配置好设备树以后还须要配置相应的驱动才能检测配置是否正确。好比dm9000网卡,就须要首先将示例信息挂接到咱们的板级设备树上,并根据芯片手册和电路原理图将相应的属性进行配置,再配置相应的驱动。须要注意的是,dm9000的地址线通常是接在片选线上的,因此设备树中就应该归属与相应片选线节点,我这里用的exynos4412,接在了bank1,因此是"<0x50000000 0x2 0x50000004 0x2>"
最终的配置结果是:

勾选相应的选项将dm9000的驱动编译进内核。

make menuconfig
[*] Networking support  ---> Networking options ---> <*> Packet socket <*>Unix domain sockets [*] TCP/IP networking [*] IP: kernel level autoconfiguration Device Drivers ---> [*] Network device support ---> [*] Ethernet driver support (NEW) ---> <*> DM9000 support File systems ---> [*] Network File Systems (NEW) ---> <*> NFS client support [*] NFS client support for NFS version 3 [*] NFS client support for the NFSv3 ACL protocol extension [*] Root file system on NFS 

执行make uImage;make dtbs,tftp下载,成功加载nfs根文件系统并进入系统,表示网卡移植成功

相关文章
相关标签/搜索