目录
一、Linux 设备树的由来
二、Linux设备树的目的
1.平台识别
2.实时配置
3.设备植入
三、Linux 设备树的使用
1.基本数据格式
2.设备树实例解析
四、使用设备树的LED 驱动
五、习题
一、Linux 设备树的由来
在 Linux 内核源码的ARM 体系结构引入设备树之前,相关的 BSP 代码中充斥了大量的平台设备(Platform Device) 代码,而这些代码大多都是重复的、杂乱的。之前的内核移植工作有很大一部分工作就是在复制一份 BSP 代码,并修改BSP 代码中和目标板中与特定硬件相关的平台设备信息。这使得ARM 体系结构的代码维护者和内核维护者在发布一个新版本内核的一段时间内有大量的工作要做。以至于 Linus Torvalds 在 2011年3月17日的ARM Linux 邮件列表中宣称“Gaah.Guys,this whole ARM thing is a f*cking pain in the ass”。这使得整个 ARM 社区不得不重新慎重地考虑这个问题,于是设备树(Device Tree,DT)被ARM 社区所采用。
但需要说明的是,在 Linux 中,PowerPC 和SPARC体系结构很早就使用了设备树这并不是一个最近才提出的概念。设备树最初是由开放固件(Open Firmware)使用的,用来向一个客户程序(通常是一个操作系统)传递数据的通信方法中的一部分内容。在运行时,客户程序通过设备树发现设备的拓扑结构,这样就不需要把硬件信息硬编码到程序中。
二、Linux设备树的目的
设备树是一个描述硬件的数据结构,它并没有什么神奇的地方,也不能把所有硬件配置的问题都解决掉。它只是提供了一种语言,将硬件配置从 Linux 内核源码中提取出来。设备树使得目标板和设备变成数据驱动的,它们必须基于传递给内核的数据进行初始化,而不是像以前一样采用硬编码的方式。理论上,这种方式可以带来较少的代码重复率,使单个内核镜像能够支持很多硬件平台。
Linux使用设备树有以下三个主要原因。
1.平台识别
第一且最重要的是,内核使用设备树中的数据去识别特定机器 (目标板,在内核中称为 machine)。最完美的情况是,内核应该与特定硬件平台无关,因为所有硬件平台的细节都由设备树来描述。然而,硬件平台并不是完美的,所以内核必须在早期初始化阶段识别机器,这样内核才有机会运行与特定机器相关的初始化序列。
在大多数情况下,机器识别是与设备树无关的,内核通过机器的 CPU或SOC 来选择初始化代码。以ARM 平台为例,setup_arch 会调用 setup_machine_fdt,后者遍历machine_desc 链表,选择最匹配设备树数据的 machine_desc 结构。这是通过查找设备树根节点的compatible 属性,并把它和 machine_desc 中的 dt_compat 列表中的各项进行比较来决定哪一个machine_desc 结构是最适合的。
compatible 属性包含一个有序的字符串列表,它以确切的机器名开始,紧跟着一个可选的 board 列表,从最匹配到其他匹配类型。以Samsung 的 Exynos4x12 系列的SoC芯片为例,在arch/arm/mach-exynos/mach-exynos4-dt.c 文件中的dt_compat 列表定义如下。
static char const *exynos4_dt_compat[] _initdata = {
"samsung,exynos4210"
"samsung,exynos4212"
"samsung,exynos4412",
NULl
};
而在 origen 目标板的设备树源文件 arch/arm/boot/dts/exynos4412-origen.dts 中包含的exynos4412.dtsi文件中指定的 compatible 属性如下。
compatible = "samsung,exynos4412";
这样在内核启动过程中就可以通过传递的设备树数据找到匹配的机器所对应的machine_desc 结构,如果没找到则返回 NULL。采用这种方式,可以使用一个 machine_desc支持多个机器,从而降低了代码的重复率。当然,对初始化有特殊要求的机器的初始化过程应该有所区别,这可以通过其他的属性或一些钩子函数来解决。
2.实时配置
在大多数情况下,设备树是固件与内核之间进行数据通信的唯一方式,所以也用于传递实时或配置数据给内核,比如内核参数、initrd 镜像的地址等。大多数这种数据被包含在设备树的/chosen节点,形如:
chosen { bootargs = "console=ttys0,115200 1oglevel=8";
initrd-start = <0xc8000000>;
initrd-end = <0xc8200000>;
};
bootargs 属性包含内核参数,initrd-*属性定义了 initrd 文件的首地址和大小。chosen
节点也有可能包含任意数量的描述平台特殊配置的属性。
在早期的初始化阶段,页表建立之前,与体系结构初始化相关的代码会多次联合使用不同的辅助回调函数去调用 of_scan_flat_dt 来解设备数据。of_scan_flat_dt 遍历设备树并利用辅助函数来提取需要的信息。通常,early_init_dt_scan_chosen 辅助函数用于解析包括内核参数的 chosen 节点:early_init_dt_scan_root 辅助函数用于初始化设备树的地址空间模型; early_init_dt_scan_memory 辅助函数用于决定可用内存的大小和地址。
在ARM平台,setup_machine_fdt函数负责在选取到正确的machine_desc 结构之后进行早期的设备树遍历。
3.设备植入
经过板子识别和早期配置数据解析之后,内核进一步进行初始化。期间unflatten_device_tree 函数被调用,将设备树的数据转换成一种更有效的实时形式。同时机器特殊的启动钩子函数也会被调用,例如machine_desc 中的init_early函数、init_irq函数、init_machine函数等。通过名称我们可以猜想到,init_early函数会在早期初始化时被执行,init_irg 函数用于初始化中断处理。利用设备树并没有改变这些函数的行为和功能。如果设备树被提供那么不管是init_early 函数还是init_irg函数都可以调用任何设备树查找函数去获取额外的平台信息。不过 init_machine 函数却需要更多地关注,在 arch/armmach-exynos/mach-exynos4-dtc 文件中init machine函数有如下一条语句:
of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);
of_platform_populate 函数的作用是遍历设备树中的节点,把匹配的节点转换成平台设备,然后注册到内核中。
三、Linux 设备树的使用
1.基本数据格式
在 Linux 中,设备树文件的类型有,dts、.dtsi和dtb。其中,dtsi 是被包含的设备树源文件,类似于 C 语言中的头文件;.dts 是设备树源文件,可以包含其他.dtsi 文件,由dtc编译生成.dtb 文件。
设备树是一个包含节点和属性的简单树状结构。属性就是键值对,而节点可以同时包含属性和子节点。下面就是一个.dts 格式的简单设备树:
/ {
node1 {
a-string-property = "A string";
a-string-list-property = "first string","second string";
a-byte-data-property = [0x01 0x23 0x34 0x56];
child-nodel {
first-child-property;
second-child-property = <1>;
a-string-property = "Hello,world";
};
child-node2 {
};
};
node2 {
an-empty-property;
a-cell-property = <1>;/* each number (cell) is uint32 */
child-node1 {
};
};
};
该设备树包含了下面的内容
- 一个单独的根节点:/。
- 两个子节点:node1和node2。
- 两个nodel的子节点:child-node 和child-node2
- 一堆分散在设备树中的属性。
其中,属性是简单的键值对,它的值可以为空或包含一个任意字节流。在设备树源文件中有以下几个基本的数据表示形式。
- 文本字符串(无结束符):可以用双引号表示,如a-string-property="A string"
- cells:32位无符号整数,用角括号限定,如 second-child-property =<l>。
- 二进制数据:用方括号限定,如a-byte-data-property =[0x010x230x340x56]。
- 混合表示:使用逗号连在一起,如mixed-property ="astring”,[0x010x230x450x67],
<0x12345678>。
- 字符串列表:使用逗号连在一起,如string-list="red fish","blue fish"。
2.设备树实例解析
下面是从arch/arm/boot/dts/exynos4.dtsi设备树源文件中抽取出来的内容:
/*
* Samsung's Exynos4 SoC series common device tree source
*
* Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
* http://www.samsung.com
* Copyright (c) 2010-2011 Linaro Ltd.
* www.linaro.org
*
* Samsung's Exynos4 SoC series device nodes are listed in this file. Particular
* SoCs from Exynos4 series can include this file and provide values for SoCs
* specfic bindings.
*
* Note: This file does not include device nodes for all the controllers in
* Exynos4 SoCs. As device tree coverage for Exynos4 increases, additional
* nodes can be added to this file.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include "skeleton.dtsi"
/ {
interrupt-parent = <&gic>;
aliases {
spi0 = &spi_0;
spi1 = &spi_1;
spi2 = &spi_2;
i2c0 = &i2c_0;
i2c1 = &i2c_1;
i2c2 = &i2c_2;
i2c3 = &i2c_3;
i2c4 = &i2c_4;
i2c5 = &i2c_5;
i2c6 = &i2c_6;
i2c7 = &i2c_7;
csis0 = &csis_0;
csis1 = &csis_1;
fimc0 = &fimc_0;
fimc1 = &fimc_1;
fimc2 = &fimc_2;
fimc3 = &fimc_3;
};
chipid@10000000 {
compatible = "samsung,exynos4210-chipid";
reg = <0x10000000 0x100>;
};
mipi_phy: video-phy@10020710 {
compatible = "samsung,s5pv210-mipi-video-phy";
reg = <0x10020710 8>;
#phy-cells = <1>;
};
pd_mfc: mfc-power-domain@10023C40 {
compatible = "samsung,exynos4210-pd";
reg = <0x10023C40 0x20>;
};
pd_g3d: g3d-power-domain@10023C60 {
compatible = "samsung,exynos4210-pd";
reg = <0x10023C60 0x20>;
};
pd_lcd0: lcd0-power-domain@10023C80 {
compatible = "samsung,exynos4210-pd";
reg = <0x10023C80 0x20>;
};
pd_tv: tv-power-domain@10023C20 {
compatible = "samsung,exynos4210-pd";
reg = <0x10023C20 0x20>;
};
pd_cam: cam-power-domain@10023C00 {
compatible = "samsung,exynos4210-pd";
reg = <0x10023C00 0x20>;
};
pd_gps: gps-power-domain@10023CE0 {
compatible = "samsung,exynos4210-pd";
reg = <0x10023CE0 0x20>;
};
gic: interrupt-controller@10490000 {
compatible = "arm,cortex-a9-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x10490000 0x1000>, <0x10480000 0x100>;
};
combiner: interrupt-controller@10440000 {
compatible = "samsung,exynos4210-combiner";
#interrupt-cells = <2>;
interrupt-controller;
reg = <0x10440000 0x1000>;
};
sys_reg: syscon@10010000 {
compatible = "samsung,exynos4-sysreg", "syscon";
reg = <0x10010000 0x400>;
};
camera {
compatible = "samsung,fimc", "simple-bus";
status = "disabled";
#address-cells = <1>;
#size-cells = <1>;
ranges;
clock_cam: clock-controller {
#clock-cells = <1>;
};
fimc_0: fimc@11800000 {
compatible = "samsung,exynos4210-fimc";
reg = <0x11800000 0x1000>;
interrupts = <0 84 0>;
clocks = <&clock 256>, <&clock 128>;
clock-names = "fimc", "sclk_fimc";
samsung,power-domain = <&pd_cam>;
samsung,sysreg = <&sys_reg>;
status = "disabled";
};
fimc_1: fimc@11810000 {
compatible = "samsung,exynos4210-fimc";
reg = <0x11810000 0x1000>;
interrupts = <0 85 0>;
clocks = <&clock 257>, <&clock 129>;
clock-names = "fimc", "sclk_fimc";
samsung,power-domain = <&pd_cam>;
samsung,sysreg = <&sys_reg>;
status = "disabled";
};
fimc_2: fimc@11820000 {
compatible = "samsung,exynos4210-fimc";
reg = <0x11820000 0x1000>;
interrupts = <0 86 0>;
clocks = <&clock 258>, <&clock 130>;
clock-names = "fimc", "sclk_fimc";
samsung,power-domain = <&pd_cam>;
samsung,sysreg = <&sys_reg>;
status = "disabled";
};
fimc_3: fimc@11830000 {
compatible = "samsung,exynos4210-fimc";
reg = <0x11830000 0x1000>;
interrupts = <0 87 0>;
clocks = <&clock 259>, <&clock 131>;
clock-names = "fimc", "sclk_fimc";
samsung,power-domain = <&pd_cam>;
samsung,sysreg = <&sys_reg>;
status = "disabled";
};
csis_0: csis@11880000 {
compatible = "samsung,exynos4210-csis";
reg = <0x11880000 0x4000>;
interrupts = <0 78 0>;
clocks = <&clock 260>, <&clock 134>;
clock-names = "csis", "sclk_csis";
bus-width = <4>;
samsung,power-domain = <&pd_cam>;
phys = <&mipi_phy 0>;
phy-names = "csis";
status = "disabled";
#address-cells = <1>;
#size-cells = <0>;
};
csis_1: csis@11890000 {
compatible = "samsung,exynos4210-csis";
reg = <0x11890000 0x4000>;
interrupts = <0 80 0>;
clocks = <&clock 261>, <&clock 135>;
clock-names = "csis", "sclk_csis";
bus-width = <2>;
samsung,power-domain = <&pd_cam>;
phys = <&mipi_phy 2>;
phy-names = "csis";
status = "disabled";
#address-cells = <1>;
#size-cells = <0>;
};
};
watchdog@10060000 {
compatible = "samsung,s3c2410-wdt";
reg = <0x10060000 0x100>;
interrupts = <0 43 0>;
clocks = <&clock 345>;
clock-names = "watchdog";
status = "disabled";
};
rtc@10070000 {
compatible = "samsung,s3c6410-rtc";
reg = <0x10070000 0x100>;
interrupts = <0 44 0>, <0 45 0>;
clocks = <&clock 346>;
clock-names = "rtc";
status = "disabled";
};
keypad@100A0000 {
compatible = "samsung,s5pv210-keypad";
reg = <0x100A0000 0x100>;
interrupts = <0 109 0>;
clocks = <&clock 347>;
clock-names = "keypad";
status = "disabled";
};
sdhci@12510000 {
compatible = "samsung,exynos4210-sdhci";
reg = <0x12510000 0x100>;
interrupts = <0 73 0>;
clocks = <&clock 297>, <&clock 145>;
clock-names = "hsmmc", "mmc_busclk.2";
status = "disabled";
};
sdhci@12520000 {
compatible = "samsung,exynos4210-sdhci";
reg = <0x12520000 0x100>;
interrupts = <0 74 0>;
clocks = <&clock 298>, <&clock 146>;
clock-names = "hsmmc", "mmc_busclk.2";
status = "disabled";
};
sdhci@12530000 {
compatible = "samsung,exynos4210-sdhci";
reg = <0x12530000 0x100>;
interrupts = <0 75 0>;
clocks = <&clock 299>, <&clock 147>;
clock-names = "hsmmc", "mmc_busclk.2";
status = "disabled";
};
sdhci@12540000 {
compatible = "samsung,exynos4210-sdhci";
reg = <0x12540000 0x100>;
interrupts = <0 76 0>;
clocks = <&clock 300>, <&clock 148>;
clock-names = "hsmmc", "mmc_busclk.2";
status = "disabled";
};
ehci@12580000 {
compatible = "samsung,exynos4210-ehci";
reg = <0x12580000 0x100>;
interrupts = <0 70 0>;
clocks = <&clock 304>;
clock-names = "usbhost";
status = "disabled";
};
ohci@12590000 {
compatible = "samsung,exynos4210-ohci";
reg = <0x12590000 0x100>;
interrupts = <0 70 0>;
clocks = <&clock 304>;
clock-names = "usbhost";
status = "disabled";
};
mfc: codec@13400000 {
compatible = "samsung,mfc-v5";
reg = <0x13400000 0x10000>;
interrupts = <0 94 0>;
samsung,power-domain = <&pd_mfc>;
clocks = <&clock 273>;
clock-names = "mfc";
status = "disabled";
};
serial@13800000 {
compatible = "samsung,exynos4210-uart";
reg = <0x13800000 0x100>;
interrupts = <0 52 0>;
clocks = <&clock 312>, <&clock 151>;
clock-names = "uart", "clk_uart_baud0";
status = "disabled";
};
serial@13810000 {
compatible = "samsung,exynos4210-uart";
reg = <0x13810000 0x100>;
interrupts = <0 53 0>;
clocks = <&clock 313>, <&clock 152>;
clock-names = "uart", "clk_uart_baud0";
status = "disabled";
};
serial@13820000 {
compatible = "samsung,exynos4210-uart";
reg = <0x13820000 0x100>;
interrupts = <0 54 0>;
clocks = <&clock 314>, <&clock 153>;
clock-names = "uart", "clk_uart_baud0";
status = "disabled";
};
serial@13830000 {
compatible = "samsung,exynos4210-uart";
reg = <0x13830000 0x100>;
interrupts = <0 55 0>;
clocks = <&clock 315>, <&clock 154>;
clock-names = "uart", "clk_uart_baud0";
status = "disabled";
};
i2c_0: i2c@13860000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "samsung,s3c2440-i2c";
reg = <0x13860000 0x100>;
interrupts = <0 58 0>;
clocks = <&clock 317>;
clock-names = "i2c";
pinctrl-names = "default";
pinctrl-0 = <&i2c0_bus>;
status = "disabled";
};
i2c_1: i2c@13870000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "samsung,s3c2440-i2c";
reg = <0x13870000 0x100>;
interrupts = <0 59 0>;
clocks = <&clock 318>;
clock-names = "i2c";
pinctrl-names = "default";
pinctrl-0 = <&i2c1_bus>;
status = "disabled";
};
i2c_2: i2c@13880000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "samsung,s3c2440-i2c";
reg = <0x13880000 0x100>;
interrupts = <0 60 0>;
clocks = <&clock 319>;
clock-names = "i2c";
status = "disabled";
};
i2c_3: i2c@13890000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "samsung,s3c2440-i2c";
reg = <0x13890000 0x100>;
interrupts = <0 61 0>;
clocks = <&clock 320>;
clock-names = "i2c";
status = "disabled";
};
i2c_4: i2c@138A0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "samsung,s3c2440-i2c";
reg = <0x138A0000 0x100>;
interrupts = <0 62 0>;
clocks = <&clock 321>;
clock-names = "i2c";
status = "disabled";
};
i2c_5: i2c@138B0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "samsung,s3c2440-i2c";
reg = <0x138B0000 0x100>;
interrupts = <0 63 0>;
clocks = <&clock 322>;
clock-names = "i2c";
status = "disabled";
};
i2c_6: i2c@138C0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "samsung,s3c2440-i2c";
reg = <0x138C0000 0x100>;
interrupts = <0 64 0>;
clocks = <&clock 323>;
clock-names = "i2c";
status = "disabled";
};
i2c_7: i2c@138D0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "samsung,s3c2440-i2c";
reg = <0x138D0000 0x100>;
interrupts = <0 65 0>;
clocks = <&clock 324>;
clock-names = "i2c";
status = "disabled";
};
spi_0: spi@13920000 {
compatible = "samsung,exynos4210-spi";
reg = <0x13920000 0x100>;
interrupts = <0 66 0>;
dmas = <&pdma0 7>, <&pdma0 6>;
dma-names = "tx", "rx";
#address-cells = <1>;
#size-cells = <0>;
clocks = <&clock 327>, <&clock 159>;
clock-names = "spi", "spi_busclk0";
pinctrl-names = "default";
pinctrl-0 = <&spi0_bus>;
status = "disabled";
};
spi_1: spi@13930000 {
compatible = "samsung,exynos4210-spi";
reg = <0x13930000 0x100>;
interrupts = <0 67 0>;
dmas = <&pdma1 7>, <&pdma1 6>;
dma-names = "tx", "rx";
#address-cells = <1>;
#size-cells = <0>;
clocks = <&clock 328>, <&clock 160>;
clock-names = "spi", "spi_busclk0";
pinctrl-names = "default";
pinctrl-0 = <&spi1_bus>;
status = "disabled";
};
spi_2: spi@13940000 {
compatible = "samsung,exynos4210-spi";
reg = <0x13940000 0x100>;
interrupts = <0 68 0>;
dmas = <&pdma0 9>, <&pdma0 8>;
dma-names = "tx", "rx";
#address-cells = <1>;
#size-cells = <0>;
clocks = <&clock 329>, <&clock 161>;
clock-names = "spi", "spi_busclk0";
pinctrl-names = "default";
pinctrl-0 = <&spi2_bus>;
status = "disabled";
};
pwm@139D0000 {
compatible = "samsung,exynos4210-pwm";
reg = <0x139D0000 0x1000>;
interrupts = <0 37 0>, <0 38 0>, <0 39 0>, <0 40 0>, <0 41 0>;
clocks = <&clock 336>;
clock-names = "timers";
#pwm-cells = <2>;
status = "disabled";
};
amba {
#address-cells = <1>;
#size-cells = <1>;
compatible = "arm,amba-bus";
interrupt-parent = <&gic>;
ranges;
pdma0: pdma@12680000 {
compatible = "arm,pl330", "arm,primecell";
reg = <0x12680000 0x1000>;
interrupts = <0 35 0>;
clocks = <&clock 292>;
clock-names = "apb_pclk";
#dma-cells = <1>;
#dma-channels = <8>;
#dma-requests = <32>;
};
pdma1: pdma@12690000 {
compatible = "arm,pl330", "arm,primecell";
reg = <0x12690000 0x1000>;
interrupts = <0 36 0>;
clocks = <&clock 293>;
clock-names = "apb_pclk";
#dma-cells = <1>;
#dma-channels = <8>;
#dma-requests = <32>;
};
mdma1: mdma@12850000 {
compatible = "arm,pl330", "arm,primecell";
reg = <0x12850000 0x1000>;
interrupts = <0 34 0>;
clocks = <&clock 279>;
clock-names = "apb_pclk";
#dma-cells = <1>;
#dma-channels = <8>;
#dma-requests = <1>;
};
};
fimd: fimd@11c00000 {
compatible = "samsung,exynos4210-fimd";
interrupt-parent = <&combiner>;
reg = <0x11c00000 0x20000>;
interrupt-names = "fifo", "vsync", "lcd_sys";
interrupts = <11 0>, <11 1>, <11 2>;
clocks = <&clock 140>, <&clock 283>;
clock-names = "sclk_fimd", "fimd";
samsung,power-domain = <&pd_lcd0>;
status = "disabled";
};
};
(1)包含其他的“.dtsi”文件,如:
#include "skeleton.dtsi"。
(2)节点名称,是一个“<名称>[@<设备地址>]”形式的名字。方括号中的内容不是必需的。“名称”是一个不超过 31 位的简单 ascii 字符串,应该根据它所体现的设备来进行命名。如果该节点描述的设备有一个地址就应该加上单元地址,通常,设备地址就是用来访问该设备的主地址,并且该地址也在节点的reg 属性中列出。关于 reg 属性将会在后面描述。同级节点命名必须是唯一的,但只要地址不同,多个节点也可以使用一样的通用名称。节点名称的例子如下:
serial@13800000
serial@13810000
(3)系统中每个设备都表示为一个设备树节点,每个设备树节点都拥有一个compatible 属性。
(4)compatible 属性是操作系统用来决定使用哪个设备驱动来绑定到一个设备上的关键因素。compatible 是一个字符串列表,第一个字符串指定了这个节点所表示的确切的设备,该字符串的格式为:"<制造商>,<型号>",其余的字符串则表示其他与之兼容的设备
例如:
compatible = "arm,p1330","arm,primecell";
(5)可编址设备使用以下属性将地址信息编码进设备树:
reg
#address-cells
#size-cells
每个可编址设备都有一个reg,它是一个元组表,形式为:reg =<地址1 长度1[地址 2长度 2][地址3 长度3]...>。每个元组都表示该设备使用的一个地址范围。每个地址值是一个或多个 32 位整型数列表,称为 cell。同样,长度值也可以是一个 cell 列表或者为空。由于地址和长度字段都是可变大小的变量,那么父节点的 #address-cells 和#size-cells 属性就用来声明各个字段的 cell 的数量。换话说,正确解释一个 reg 属性需要用到父节点的#address-cells 和#size-cells 的值。如在 arch/arm/boot/dts/exynos4412-origen.dts 文件中I2C 设备的相应描述:
12c@13860000 {
#address-cells = <1>;
#size-cells=<0>;
samsung,12c-sda-delay = <100>;
samsung,12c-max-bus-freq=<20000>;
pinctr1-0 = <&i2c0_bus>;
pinctrl-names = "default";
status = "okay";
s5m8767_pmic@66 {
compatible = "samsung,s5m8767-pmic";
reg=<0x66>;
......
其中,I2C 主机控制器是一个父节点,地址的长度为一个 32 位整型数,地址长度为0。s5m8767_pmic是12C 主机控制器下面的一个子节点,其地址为0x66。按照惯例,如果一个节点有 reg 属性,那么该节点的名字就必须包含设备地址,这个设备地址就是 reg属性里第一个地址值。
关于设备地址还要讨论下面三个方面的内容:
- 内存映射设备。
内存映射的设备应该有地址范围,对于32位的地址可以用1个 cell来指定地址值用一个 cell来指定范围。而对于64 位的地址就应该用两个 cell来指定地址值。还有一种内存映射设备的地址表示方式,就是基地址、偏移和长度。在这种方式中,地址也是用两个cell来表示。
2.非内存映射设备。
有些设备没有被映射到 CPU 的存储器总线上,虽然这些设备可以有一个地址范围,但它们并不是由CPU直接访问。取而代之的是,父设备的驱动程序会代表 CPU 执行间接访问。这类设备的典型例子就包括上面提到的I2C 设备,NAND Flash 也属于这类设备
3.范围(地址转换)。
根节点的地址空间是从CPU 的视角进行描述的,根节点的直接子节点使用的也是这个地址域,如 chipid@10000000。但是非根节点的直接子节点就没有使用这个地址域,于是需要把这个地址进行转换,ranges 这个属性就用于此目的。如在 arch/arm/boot/dts/hi3620.dtsi文件中有下面一段描述。
sysctrl: system-controller@802000 {
compatible = "hisilicon,sysctrl";
#address-cells = <1>;
#size-cells = <1>;
ranges = <0 0x802000 0x1000>;
reg = <0x802000 0x1000>;
smp-offset = <0x31c>;
resume-offset = <0x308>;
reboot-offset = <0x4>;
clock:clock@0 {
compatible = "hisilicon,hi3620-clock";
reg = <0 0x10000>;
#clock-cells = <1>;
};
};
"sysctrl: system-controller@802000”这个节点是“clock: clock@0”的父节点,在父节点中定义了一个地址范围,这个地址范围由“<子地址 父地址 子地址空间区域大小>”这样一个元组来描述。所以“<0 0x802000 0x1000>”表示的是子地址0 被映射在父地址的0x802000-0x802FFF 处。而“clock: clok@0”这个子节点刚好使用了这个地址。有些时候,这种映射也是一对一的,即子节点使用和父节点一样的地址域,这可以通过-个空的ranges 属性来实现。如:
amba {
#address-cells = <1>;
#size-cells= <1>;
compatible="arm,amba-bus";
interrupt-parent = <&gic>;
ranges;
pdma0:pdma@12680000 {
compatible = "arm,pl330","arm,primecell";
reg = <0x12680000 0x1000>;
interrupts = <0 35 0>;
clocks=<&clock 292>;
clock-names = "apb_pclk";
#dma-cells = <1>;
#dma-channels = <8>;
#dma-requests = <32>;
};
“pdma0: pdma@12680000”子节点使用的就是和“amba”父节点一样的地址域
(6)描述中断连接需要四个属性。
interrupt-controller:一个空的属性,用来定义该节点是一个接收中断的设备,即是个中断控制器。
#interrupt-cells:一个中断控制器节点的属性,声明了该中断控制器的中断指示符中cell 的个数,类似于#address-cells。
interrupt-parent:一个设备节点的属性,指向设备所连接的中断控制器。如果这个设备节点没有该属性,那么这个节点继承父节点的这个属性。
interrupts:一个设备节点的属性,含一个中断指示符的列表,对应于该设备上的每个中断输出信号
gic:interrupt-controller@10490000 {
compatible = "arm,cortex-a9-gic"
#interrupt-cells = <3>;
interrupt-controller;
reg=<0x104900000x1000>,<0x1080000 0x100>;
};
上面的节点表示一个中断控制器,用于接收中断。中断指示符占3个 cell.
amba {
#address-cells = <1>;
#size-cells = <1>;
compatible = "arm,amba-bus";
interrupt-parent = <&gic>;
ranges;
pdma0: pdma@12680000{
compatible = "arm,p1330”,"arm,primecell";
reg = <0x12680000 0x1000>;
interrupts = <0 35 0>;
clocks = <&clock 292>;
clock-names = "apb_pclk";
#dma-cells=<1>;
#dma-channels = <8>;
#dma-requests = <32>;
};
“amba”节点是一个中断设备,产生的中断连接到“gic”中断控制器,“pdma0:pdma@12680000”是一个“amba”的子节点,继承了父节点的 interrupt-parent 属性,即该设备产生的中断也连接在“gic”中断控制器上。中断指示符占 3 个 cell,“pdma0:pdma@12680000”节点的中断指示符是“<0 35 0>”,其意义是查看内核中的相应文档。因为GIC 是ARM公司开发的一款中断控制器,查看 Documentation/devicetree/bindingsarm/gic.txt 内核文档可知,第一个 cell 是中断类型,0是 SPI,共享的外设中断,即这个中断由外设产生,可以连接到一个 SoC 中的多个ARM核;1是PPI,私有的外设中断即这个中断由外设产生,但只能连接到一个 SoC 中的特定ARM 核。第二个 cell 是中断号。第三个 cell 是中断的触发类型,0表示不关心。
(7)aliases 节点用于指定节点的别名。因为引用一个节点要使用全路径,当子节点离根节点较远时,节点名就会显得比较冗长,定义一个别名则比较方便。下面把 spi 0这个节点定义了一个别名“spi0”。
aliases {
spi0 = &spi0;
...
(8)chosen 节点并不代表一个真正的设备,只是一个为固件和操作系统传递数据的地方,如引导参数。chosen 节点里的数据也不代表硬件。如在 arch/arm/boot/dts/exynos4412-origen.dts 文件中的 chosen 节点定义如下:
chosen {
bootargs = "console=ttySAC2,115200";
};
(9)设备特定数据,用于定义特定于某个具体设备的一些属性。这些属性可以自由定义,但是新的设备特定属性的名字都应该使用制造商前缀,以避免和现有标准属性名相冲突。另外,属性和子节点的含义必须存档在 binding 文档中,以便设备驱动程序的程序员知道如何解释这些数据。在内核源码的 Documentation/devicetree/bindings/目录中包含了大量的 binding 文档,当发现设备树中的一些属性不能理解时,在该目录下查看相应的文档都能找到答案。
四、使用设备树的LED 驱动
使用设备树是内核的一个必然趋势,目前内核中除了较早的目标板在使用平台设备新的目标板几乎都使用了设备树。既然如此,我们接下来就把之前的 LED 驱动改造过来首先要做的就是在设备树源文件中添加相应的 LED设备树节点,修改 arch/armboot/dts/exynos4412-fs4412.dts,加入以下代码。
fsled2@11000C40{
compatible = "fs4412,fsled";
reg = <0x11000C40 0x8>;
id = <2>;
pin = <7>;
};
fsled3@11000C20 {
compatible = "fs4412,fsled";
reg = <0x11000C20 0x8>;
id = <3>;
pin = <0>;
};
fsled4@114001E0 {
compatible = "fs4412,fsled";
reg = <0x114001E0 0x8>;
id = <4>;
pin = <4>;
};
fsled5@114001E0 {
compatible = "fs4412,fsled";
reg = <0x114001E0 0x8>;
id = <5>;
pin = <5>;
};
上面的代码添加了4个LED 的设备树节点,compatible 都为“fs4412,fsled”;reg 属性是各自的I/O 内存;id 属性是自定义属性,表示设备的d 号;pin 属性也是自定义属性表示使用的 GPIO管脚。
代码修改后,使用下面的命令重新编译设备树文件,并且将编译的结果复制到TFTP服务器指定的目录下。
# make ARCH=arm dtbs
# cp arch/arm/boot/dts/exynos4412-fs4412.dtb ~/tftpboot/
接下来就是针对驱动的修改,主要代码如下
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include "fsled.h"
#define FSLED_MAJOR 256
#define FSLED_DEV_NAME "fsled"
struct fsled_dev {
unsigned int __iomem *con;
unsigned int __iomem *dat;
unsigned int pin;
atomic_t available;
struct cdev cdev;
};
static int fsled_open(struct inode *inode, struct file *filp)
{
struct fsled_dev *fsled = container_of(inode->i_cdev, struct fsled_dev, cdev);
filp->private_data = fsled;
if (atomic_dec_and_test(&fsled->available))
return 0;
else {
atomic_inc(&fsled->available);
return -EBUSY;
}
}
static int fsled_release(struct inode *inode, struct file *filp)
{
struct fsled_dev *fsled = filp->private_data;
writel(readl(fsled->dat) & ~(0x1 << fsled->pin), fsled->dat);
atomic_inc(&fsled->available);
return 0;
}
static long fsled_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct fsled_dev *fsled = filp->private_data;
if (_IOC_TYPE(cmd) != FSLED_MAGIC)
return -ENOTTY;
switch (cmd) {
case FSLED_ON:
writel(readl(fsled->dat) | (0x1 << fsled->pin), fsled->dat);
break;
case FSLED_OFF:
writel(readl(fsled->dat) & ~(0x1 << fsled->pin), fsled->dat);
break;
default:
return -ENOTTY;
}
return 0;
}
static struct file_operations fsled_ops = {
.owner = THIS_MODULE,
.open = fsled_open,
.release = fsled_release,
.unlocked_ioctl = fsled_ioctl,
};
static int fsled_probe(struct platform_device *pdev)
{
int ret;
dev_t dev;
struct fsled_dev *fsled;
struct resource *res;
ret = of_property_read_u32(pdev->dev.of_node, "id", &pdev->id);
if (ret)
goto id_err;
dev = MKDEV(FSLED_MAJOR, pdev->id);
ret = register_chrdev_region(dev, 1, FSLED_DEV_NAME);
if (ret)
goto reg_err;
fsled = kzalloc(sizeof(struct fsled_dev), GFP_KERNEL);
if (!fsled) {
ret = -ENOMEM;
goto mem_err;
}
cdev_init(&fsled->cdev, &fsled_ops);
fsled->cdev.owner = THIS_MODULE;
ret = cdev_add(&fsled->cdev, dev, 1);
if (ret)
goto add_err;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
ret = -ENOENT;
goto res_err;
}
fsled->con = ioremap(res->start, resource_size(res));
if (!fsled->con) {
ret = -EBUSY;
goto map_err;
}
fsled->dat = fsled->con + 1;
ret = of_property_read_u32(pdev->dev.of_node, "pin", &fsled->pin);
if (ret)
goto pin_err;
atomic_set(&fsled->available, 1);
writel((readl(fsled->con) & ~(0xF << 4 * fsled->pin)) | (0x1 << 4 * fsled->pin), fsled->con);
writel(readl(fsled->dat) & ~(0x1 << fsled->pin), fsled->dat);
platform_set_drvdata(pdev, fsled);
return 0;
pin_err:
iounmap(fsled->con);
map_err:
res_err:
cdev_del(&fsled->cdev);
add_err:
kfree(fsled);
mem_err:
unregister_chrdev_region(dev, 1);
reg_err:
id_err:
return ret;
}
static int fsled_remove(struct platform_device *pdev)
{
dev_t dev;
struct fsled_dev *fsled = platform_get_drvdata(pdev);
dev = MKDEV(FSLED_MAJOR, pdev->id);
iounmap(fsled->con);
cdev_del(&fsled->cdev);
kfree(fsled);
unregister_chrdev_region(dev, 1);
return 0;
}
static const struct of_device_id fsled_of_matches[] = {
{ .compatible = "fs4412,fsled", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, fsled_of_matches);
struct platform_driver pdrv = {
.driver = {
.name = "fsled",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(fsled_of_matches),
},
.probe = fsled_probe,
.remove = fsled_remove,
};
module_platform_driver(pdrv);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <e-mail>");
MODULE_DESCRIPTION("A simple character device driver for LEDs on FS4412 board");
代码第 163行至第167 行添加了一个fsled_of_matches 数组,用于和设备树的节点配,并且在平台驱动结构中将of_match_table 进行了相应的赋值。
获得 IO内存资源的方法和以前的一样,但是 id 和 pin 是自定义的属性,要获取这
两个属性的值,使用了of_property_read_u32函数,原型如下。
int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value);
np:设备节点对象地址
propname:属性的名字。
out_value:回传的属性值。
函数返回0表示成功,非0则失败。
测试方法和之前类似,只是不用再加载注册平台设备的模块了。设备树和内核及模块是分开编译的,所以,如果硬件发生改变,则只需要修改设备树并重新编译即可,内核和驱动模块都不需要重新编译,这是设备树的一个显著优点。
五、习题
1.平台设备的resource 成员用于记录( )。
[A] 平台设备的资源信息 [B] 平台设备的状态信息
2.udev 是一个工作在( )的程序。
[A]用户空间
[B]内核空间
3.mdev 会扫描( )目录下的文件来自动创建设备节点。
[A]/dev
[B] /sys/block
[C] /sys/class
4.Linux 设备树的目的是( )。
[A] 平台识别
[B] 实时配置
[C] 设备植入
5.设备树源文件中常见的基本数据类型有 ( )。
[A]文本字符串
[B] cells
[C] 二进制数据
[D]文本字符串和二进制数据的混合
[E] 字符串列表
6.编译设备树的命令是 (
A] make ulmage
[B] make dtbs