ARM Linux 设备树详细介绍(1)

        1. ARM&Device&Tree 起源

        Linus Torvalds 在 2011 年 3 月 17 日的 ARM Linux 邮件列表宣称“this whole ARM thing is a f*cking pain in the ass”,引发 ARM Linux 社区的地震,随后 ARM 社区进行了一系列 的重大修正。

        在过去的 ARM Linux 中,arch/arm/plat-xxx 和 arch/arm/mach-xxx 中充斥着大 量的垃圾代码,相当多数的代码只是在描述板级细节,而这些板级细节对于内核来讲,不 过是垃圾,如板上的 platform 设备、resource、i2c_board_info、spi_board_info 以及各种硬 件的 platform_data。读者有兴趣可以统计下常见的 s3c2410、s3c6410 等板级目录,代码量 在数万行。

        社区必须改变这种局面,于是 PowerPC 等其他体系架构下已经使用的 Flattened Device Tree(FDT)进入 ARM 社区的视野。

        Device Tree 是一种描述硬件的数据结构,它起源于 OpenFirmware (OF)。在 Linux 2.6 中,ARM 架构的板极硬件细节过多地被硬编码在 arch/arm/plat-xxx 和 arch/arm/mach-xxx,采用 Device Tree 后,许多硬件的细节可以直接透 过它传递给 Linux,而不再需要在 kernel 中进行大量的冗余编码。

        Device Tree 由一系列被命名的结点(node)和属性(property)组成,而结点本身可包 含子结点。所谓属性,其实就是成对出现的 name 和 value。在 Device Tree 中,可描述的信 息包括(原先这些信息大多被 hard code 到 kernel 中):

        1、 CPU 的数量和类别

        2、 内存基地址和大小

        3、 总线和桥

        4、 外设连接

        5、 中断控制器和中断使用情况

        6、 GPIO 控制器和 GPIO 使用情况

        7、 Clock 控制器和 Clock 使用情况

        它基本上就是画一棵电路板上 CPU、总线、设备组成的树,Bootloader 会将这棵树传 递给内核,然后内核可以识别这棵树,并根据它展开出 Linux 内核中的 platform_device、 i2c_client、spi_device 等设备,而这些设备用到的内存、IRQ 等资源,也被传递给了内核, 内核会将这些资源绑定给展开的相应的设备。

        2. Device&Tree 组成和结构

        整个 Device Tree 牵涉面比较广,即增加了新的用于描述设备硬件信息的文本格式,又 增加了编译这一文本的工具,同时 Bootloader 也需要支持将编译后的 Device Tree 传递给 Linux 内核。

        DTS (device tree source) 

        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。譬如,对于 VEXPRESS 而言, vexpress-v2m.dtsi 就被 vexpress-v2p-ca9.dts 所引用, vexpress-v2p-ca9.dts 有如下一行:

        /include/ "vexpress-v2m.dtsi"

        当然,和 C 语言的头文件类似,.dtsi 也可以 include 其他的.dtsi,譬如几乎所有的 ARM SoC 的.dtsi 都引用了 skeleton.dtsi。

        dts(或者其 include 的.dtsi)基本元素即为前文所述的结点和属性:

                

                上述.dts 文件并没有什么真实的用途,但它基本表征了一个 Device Tree 源文件的结构:

                1 个 root 结点"/"; root 结点下面含一系列子结点,本例中为"node1" 和 "node2";

                结点"node1"下又含有一系列子结点,本例中为"child-node1" 和 "child-node2";

                各结点都有一系列属性。这些属性可能为空,如" an-empty-property";可能为字符串, 如"a-string-property";可能为字符串数组,如"a-string-list-property";可能为 Cells(由 u32 整数组成),如"second-child-property",可能为二进制数,如"a-byte-data-property"。

                下面以一个最简单的 machine 为例来看如何写一个.dts 文件。假设此 machine 的配置如 下:

                1 个双核 ARM Cortex-A9 32 位处理器;

                ARM 的 local bus 上的内存映射区域分布了 2 个串口(分别位于 0x101F1000 和 0x101F2000)、GPIO 控制器(位于 0x101F3000)、SPI 控制器(位于 0x10170000)、中 断控制器(位于 0x10140000)和一个 external bus 桥;

                External bus 桥上又连接了 SMC SMC91111 Ethernet(位于 0x10100000)、I 2 C 控制器 (位于 0x10160000)、64MB NOR Flash(位于 0x30000000); External bus 桥上连接的 I 2 C 控制器所对应的 I 2 C 总线上又连接了 Maxim DS1338 实时 钟(I 2 C 地址为 0x58)。

                其对应的.dts 文件为:

                

                   

                        

                  上述.dts 文件中,root 结点"/"的 compatible 属性 compatible = "acme,coyotes-revenge";定义 了系统的名称,它的组织形式为:,。

                   Linux 内核透过 root 结点"/"的 compatible 属性即可判断它启动的是什么 machine。 在.dts 文件的每个设备,都有一个 compatible 属性,compatible 属性用户驱动和设备的 绑定。compatible 属性是一个字符串的列表,列表中的第一个字符串表征了结点代表的确 切设备,形式为",",其后的字符串表征可兼容的其他设备。

                  可以说 前面的是特指,后面的则涵盖更广的范围。如在 arch/arm/boot/dts/vexpress-v2m.dtsi 中的 Flash 结点:

                

                compatible 属性的第 2 个字符串"cfi-flash"明显比第 1 个字符串"arm,vexpress-flash"涵盖 的范围更广。

                再比如,Freescale MPC8349 SoC 含一个串口设备,它实现了国家半导体(National Semiconductor)的 ns16550 寄存器接口。则 MPC8349 串口设备的 compatible 属性为 compatible = "fsl,mpc8349-uart", "ns16550"。其中,fsl,mpc8349-uart 指代了确切的设备, ns16550 代表该设备与 National Semiconductor 的 16550 UART 保持了寄存器兼容。

                接下来 root 结点"/"的 cpus 子结点下面又包含 2 个 cpu 子结点,描述了此 machine 上的 2 个 CPU,并且二者的 compatible 属性为"arm,cortex-a9"。

                注意 cpus 和 cpus 的 2 个 cpu 子结点的命名,它们遵循的组织形式为:[@],<>中的内容是必选项,[]中的则为可选项。name 是一个 ASCII 字符串,用于描 述结点对应的设备类型,如 3com Ethernet 适配器对应的结点 name 宜为 ethernet,而不是 3com509。如果一个结点描述的设备有地址,则应该给出@unit-address。多个相同类型设 备结点的 name 可以一样,只要 unit-address 不同即可,如本例中含有 cpu@0、cpu@1 以及 serial@101f0000 与 serial@101f2000 这样的同名结点。

                设备的 unit-address 地址也经常在其 对应结点的 reg 属性中给出。 ePAPR 标准给出了结点命名的规范。 可寻址的设备使用如下信息来在 Device Tree 中编码地址信息:

                        

                其中 reg 的组织形式为 reg = , 其中的每一组 address length 表明了设备使用的一个地址范围。address 为 1 个或多个 32 位 的整型(即 cell),而 length 则为 cell 的列表或者为空(若#size-cells = 0)。address 和 length 字段是可变长的,父结点的#address-cells 和#size-cells 分别决定了子结点的 reg 属性 的 address 和 length 字段的长度。

                在本例中,root 结点的#address-cells = ;和#size-cells = ;决定了 serial、gpio、spi 等结点的 address 和 length 字段的长度分别为 1。cpus 结点的 #address-cells = ;和#size-cells = ;决定了 2 个 cpu 子结点的 address 为 1,而 length 为 空,于是形成了 2 个 cpu 的 reg = ;和 reg = ;。external-bus 结点的#address-cells = 和#size-cells = ;决定了其下的 ethernet、i2c、flash 的 reg 字段形如 reg = ;、 reg = ;和 reg = ;。其中,address 字段长度为 0,开始的第一个 cell(0、1、2)是对应的片选,第 2 个 cell(0,0,0)是相对该片选的基地址,第 3 个 cell(0x1000、0x1000、0x4000000)为 length。特别要留意的是 i2c 结点中定义的 #addresscells = ;和#size-cells = ;又作用到了 I 2 C 总线上连接的 RTC,它的 address 字段为 0x58,是设备的 I 2 C 地址。

                root 结点的子结点描述的是 CPU 的视图,因此 root 子结点的 address 区域就直接位于 CPU 的 memory 区域。但是,经过总线桥后的 address 往往需要经过转换才能对应的 CPU 的 memory 映射。external-bus 的 ranges 属性定义了经过 external-bus 桥后的地址范围如何 映射到 CPU 的 memory 区域。

        

                ranges 是地址转换表,其中的每个项目是一个子地址、父地址以及在子地址空间的大 小的映射。

                映射表中的子地址、父地址分别采用子地址空间的#address-cells 和父地址空间 的#address-cells 大小。对于本例而言,子地址空间的#address-cells 为 2,父地址空间的 #address-cells 值为 1,因此 0 0 0x10100000 0x10000 的前 2 个 cell 为 external-bus 后片选 0 上偏移 0,第 3 个 cell 表示 external-bus 后片选 0 上偏移 0 的地址空间被映射到 CPU 的 0x10100000 位置,第 4 个 cell 表示映射的大小为 0x10000。ranges 的后面 2 个项目的含义 可以类推。

                Device Tree 中还可以中断连接信息,对于中断控制器而言,它提供如下属性:                 interrupt-controller – 这个属性为空,中断控制器应该加上此属性表明自己的身份; #interrupt-cells – 与#address-cells 和 #size-cells 相似,它表明连接此中断控制器的设备的 interrupts 属性的 cell 大小。

                在整个 Device Tree 中,与中断相关的属性还包括: interrupt-parent – 设备结点透过它来指定它所依附的中断控制器的 phandle,当结点没有 指定 interrupt-parent 时,则从父级结点继承。

                对于本例而言,root 结点指定了 interruptparent = ;其对应于 intc: interrupt-controller@10140000,而 root 结点的子结点并未指 定 interrupt-parent,因此它们都继承了 intc,即位于 0x10140000 的中断控制器。

                interrupts – 用到了中断的设备结点透过它指定中断号、触发方法等,具体这个属性含 有多少个 cell,由它依附的中断控制器结点的#interrupt-cells 属性决定。而具体每个 cell 又 是什么含义,一般由驱动的实现决定,而且也会在 Device Tree 的 binding 文档中说明。

                譬 如,对于 ARM GIC 中断控制器而言,#interrupt-cells 为 3,它 3 个 cell 的具体含义 Documentation/devicetree/bindings/arm/gic.txt 就有如下文字说明:

                        ​​​​​​​        

                另外,值得注意的是,一个设备还可能用到多个中断号。对于 ARM GIC 而言,若某设 备使用了 SPI 的 168、169 号 2 个中断,而言都是高电平触发,则该设备结点的 interrupts 属性可 定义为:interrupts = <0  168  4>,<0  169   4> ;

                除了中断以外,在 ARM Linux 中 clock、GPIO、pinmux 都可以透过.dts 中的结点和属 性进行描述。

                

                DTC (device tree compiler)

                将.dts 编译为.dtb 的工具。

                DTC 的源代码位于内核的 scripts/dtc 目录,在 Linux 内核使 能了 Device Tree 的情况下,编译内核的时候主机工具 dtc 会被编译出来,对应 scripts/dtc/Makefile 中的“hostprogs-y := dtc”这一 hostprogs 编译 target。

                在 Linux 内核的 arch/arm/boot/dts/Makefile 中,描述了当某种 SoC 被选中后,哪些.dtb 文件会被编译出来,如与 VEXPRESS 对应的.dtb 包括:

                

                

                Linux 下,我们可以单独编译 Device Tree 文件。

                当我们在 Linux 内核下运行 make dtbs 时,若我们之前选择了 ARCH_VEXPRESS,上述.dtb 都会由对应的.dts 编译出来。因 为 arch/arm/Makefile 中含有一个 dtbs 编译 target 项目。                

                 Device Tree Blob (.dtb)

                dtb 是.dts 被 DTC 编译后的二进制格式的 Device Tree 描述,可由 Linux 内核解析。通 常在我们为电路板制作 NAND、SD 启动 image 时,会为.dtb 文件单独留下一个很小的区域 以存放之,之后 bootloader 在引导 kernel 的过程中,会先读取该.dtb 到内存。

                Binding

                对于 Device Tree 中的结点和属性具体是如何来描述设备的硬件细节的,一般需要文档 来进行讲解,文档的后缀名一般为.txt。这些文档位于内核的 Documentation/devicetree/bindings 目录,其下又分为很多子目录。

                

                Bootloader

                Uboot mainline 从 v1.1.3 开始支持 Device Tree,其对 ARM 的支持则是和 ARM 内核支 持 Device Tree 同期完成。

                为了使能 Device Tree,需要编译 Uboot 的时候在 config 文件中加入

                #define CONFIG_OF_LIBFDT

                在 Uboot 中,可以从 NAND、SD 或者 TFTP 等任意介质将.dtb 读入内存,假设.dtb 放 入的内存地址为 0x71000000,之后可在 Uboot 运行命令 fdt addr 命令设置.dtb 的地址,如:

                U-Boot> fdt addr 0x71000000

                fdt 的其他命令就变地可以使用,如 fdt resize、fdt print 等。 对于 ARM 来讲,可以透过 bootz kernel_addr initrd_address dtb_address 的命令来启动内 核,即 dtb_address 作为 bootz 或者 bootm 的最后一次参数,第一个参数为内核映像的地址, 第二个参数为 initrd 的地址,若不存在 initrd,可以用 -代替。

《剩下内容请继续阅读第二篇》

  • 31
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值