18.1 ARM设备树简介
- 设备舒适一种描述印鉴的数据结构,它起源于OpenFirmware(OF)
- 采用设备树前后对比:
- 采用设备树之前:ARM架构的板极硬件细节过多的被硬编码在arch/arm/plat-xxx和arch/arm/mach-xxx中
- 采用设备树之后:许多硬件细节可以直接通过它传递给Linux,而不再需要在讷河中进行大量的冗余编码
- 设备树的组成:由一系列被命名的节点(Node)和属性(Property)组成
- 节点本身有包含子节点
- 属性:成对出现的名称和值
- 设备树中可描述的信息包括:
- CPU的数量和类别
- 内存基地址和大小
- 总线和桥
- 外设链接
- 中断控制器和中断使用情况
- GPIO控制器和GPIO使用情况
- 时钟控制器核实中使用情况
- 通俗描述:
- 它基本上就是画一颗电路板上CPU、总线、设备组成的树
- bootloader会将这棵树传递给内核
- 内核可以识别这棵树,并根据他站开出Linux内核中的platform_device、i2c_client、spi_device等设备
- 这些设备用到的内存、IRQ等资源,也被传递给了内核,内核会将这些资源绑定给展开的相应的设备
- 采用设备树前后对比:
18.2 设备树的组成和结构
整个设备数牵涉买你比较广,既增加了新的用于描述设备硬件信息的文件格式,又增加了编译这个文本的工具,同时bootloader也需要支持将编译后的设备数传递给Linux内核
18.2.1 DTS、DTC和DTB等
18.2.1.1 DTS
- DTS:文件.dts是一种ASCII文本格式的设备树描述,人性化适合阅读
- .dts的基本元素为节点和属性
- ARM Linux中,一个.dts文件对应一个ARM的设备,一般放置在内核的arch/arm/boot/dts目录中
- DTS不是ARM独有的:在arch/powerpc/boot/dts、arch/openrisc/boot/dts等目录中,也有大量的.dts文件
- SOC公用的部分或多个设备共同的部分一般提炼为.dtsi,类似C的头文件,其他设备对应的.dts就包含.dtsi
- 和C语言的头文件类似,.dtsi也可以包括其他的.dtsi,譬如几乎所有的ARM SOC的.dtsi都引用了skeleton.dtsi
- 如下:设备数的模板
- 他基本表征了一个设备树源文件的结构
- 1个root节点”/”;
- root节点下面含一系列子节点,如代码中的child-node1和child-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
- 他基本表征了一个设备树源文件的结构
/ {
nodel {
a-string-property = "A string";
a-string-list-property = "first string", "second string"
a-byte-data-property = {0x01 0x23 0x34 0x56};
child-node {
first-child-property;
second-child-property = <1>
a-string-property = "Hello, world"
};
child-node2 {
};
};
node2 {
an-enpty-property;
a-cell-property = <1 2 3 4>; /* each munber (cell) is a uint32 */
child-node1 {
};
};
};
- 简单的设备数文件实例
- 假设此machine的配置如下:
- 1个双核ARM Cortex-A9 32位处理器;
- ARM的local bus上的内存映射区域分布了
- 2个串口(分别位于0x101F1000 和 0x101F2000)
- GPIO控制器(位于0x101F3000)
- SPI控制器(位于0x10170000)
- 中断控制器(位于0x10140000)
- 一个external bus桥,该桥上又连接了
- SMC SMC91111 Ethernet(位于0x10100000)
- 64MB NOR Flash(位于0x30000000)
- I2C控制器(位于0x10160000),该I2C控制器所对应的I2C总线上又连接了
- Maxim DS1338实时钟(I2C地址为0x58)
- 其对应的.dts文件如下:
- 从代码可知,external-bus是根节点的子节点,I2C又是external-bus的子节点,RTC有进一步是I2C的子节点
- 假设此machine的配置如下:
/ {
compatible = "acme,coyotes-revenge";
#address-cells = <1>;
#size-cells = <1>;
interrupt-parent = <&intc>;
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a9";
reg = <0>;
};
cpu@1 {
compatible = "arm,cortex-a9";
reg = <1>;
};
};
serial@101f0000 {
compatible = "arm,pl011";
reg = <0x101f0000 0x1000 >;
interrupts = < 1 0 >;
};
serial@101f2000 {
compatible = "arm,pl011";
reg = <0x101f2000 0x1000 >;
interrupts = < 2 0 >;
};
gpio@101f3000 {
compatible = "arm,pl061";
reg = <0x101f3000 0x1000
0x101f4000 0x0010>;
interrupts = < 3 0 >;
};
intc: interrupt-controller@10140000 {
compatible = "arm,pl190";
reg = <0x10140000 0x1000 >;
interrupt-controller;
#interrupt-cells = <2>;
};
spi@10115000 {
compatible = "arm,pl022";
reg = <0x10115000 0x1000 >;
interrupts = < 4 0 >;
};
external-bus {
#address-cells = <2>
#size-cells = <1>;
ranges = <0 0 0x10100000 0x10000 // Chipselect 1, Ethernet
1 0 0x10160000 0x10000 // Chipselect 2, i2c controller
2 0 0x30000000 0x1000000>; // Chipselect 3, NOR Flash
ethernet@0,0 {
compatible = "smc,smc91c111";
reg = <0 0 0x1000>;
interrupts = < 5 2 >;
};
i2c@1,0 {
compatible = "acme,a1234-i2c-bus";
#address-cells = <1>;
#size-cells = <0>;
reg = <1 0 0x1000>;
interrupts = < 6 2 >;
rtc@58 {
compatible = "maxim,ds1338";
reg = <58>;
interrupts = < 7 3 >;
};
};
flash@2,0 {
compatible = "samsung,k8f1315ebm", "cfi-flash";
reg = <2 0 0x4000000>;
};
};
};
18.2.1.2 DTC(Device Tree Compiler)
- DTC是将.dts编译为.dtb的工具
- DTC的源代码位于内核的scripts/dtc目录中
- 在Linux内核使能了Device Tree的情况下,编译内核的时候主机工具dtc会被编译出来,对应scripts/dtc/Makefile中的“hostprogs-y := dtc”这一hostprogs编译target
- Ubuntu中单独安装DTC:sudo apt-get install device-tree-compiler
- 在Linux内核的arch/arm/boot/dts/Makefile中,描述了当某种SOC被选中后,哪些.dtb文件会被编译出来
- 如与VEXPRESS对应的.dtb如下:
- 在Linux下可以单独编译设备树文件,当在Linux内核下运行make dtbs时,若之前选择了ARCH_VEXPRESS,上述.dtb都会有对应的.dts编译出来,因为arch/arm/Makefile中包含有一个.dtbs编译目标项目
- 如与VEXPRESS对应的.dtb如下:
dtb-$(CONFIG_ARCH_VEXPRESS) += vexpress-v2p-ca5s.dtb \
vexpress-v2p-ca9.dtb \
vexpress-v2p-ca15-tc1.dtb \
vexpress-v2p-ca15_a7.dtb \
xenvm-4.2.dtb
18.2.1.3 DTB(Device Tree Blob)
- 文件.dtb是.dts被DTC编译后的二进制格式的设备树描述,可由Linux内核解析,UBOOT也可以识别
- 使用.dtb两种方式:
- 在制作NAND、SD启动映像时会留有一小片区域给.dtb文件单独存放,bootloader在引导内核时会先读取该.dtb到内存
- 将.dtb直接和zImage绑定在一起做成一个映像文件,此时内核编译时需要使能CONFIG_ARM_APPENDED_DTB选项
- 使用.dtb两种方式:
18.2.1.4 绑定(Bingding)
- 设备树绑定文档(.txt):描述对应节点的兼容性、必要的属性和可选的属性
- 文档位置:内核的Documentation/devicetree/bindings目录
- 其下又分为很多子目录,譬如,Documentation/devicetree/bindings/i2c/i2c-xiic.txt描述了Xilinx的I2C控制器
- 文档主要内容:
- 关于该模块的最基本描述
- 必需属性(Required Properties)的描述
- 可选属性(Optional properties)的描述
- 一个实例
- 文档位置:内核的Documentation/devicetree/bindings目录
18.2.1.5 Bootloader
- 使能设备树的bootloader配置
- 编译UBOOT的时候在config文件中加入:#define CONFIG_OF_LIBFDT
- 在UBOOT中,可以从NAND、SD或者TFTP等任意介质中将.dtb读入内存
- 例如:假设.dtb放入的内存地址为0x71000000,之后可在UBOOT中运行fdt addr命令设置.dtb的地址
- fdt的其他命令就变得可以使用,如fdt resize、fdt print等
- 对于ARM来讲,可以通过bootz kernel_addr initrd_address dtb_address的命令来启动内核
- dtb_address作为bootz或者bootm的最后一次参数,第一个参数为内核映像的地址,第二个参数为initrc的地址
- 例如:假设.dtb放入的内存地址为0x71000000,之后可在UBOOT中运行fdt addr命令设置.dtb的地址
UBoot > fdt addr 0x71000000
18.2.2 根节点兼容性
- Linux内核通过根节点“/”的兼容性即可判断它启动的是什么设备
- 组织形式:,
- 兼容性的包含成员
- 首个兼容性的字符串是板子级别的名字
- 后面一个兼容性是芯片级别的名字
- 两个字符串兼容性实例如例1:
- 代码中各个电路板的共性是兼容于arm,vexpress,尔特性分别兼容于:arm,vexpress,v2p-ca9/ca5s/ca15_a7
- 多个字符串兼容性实例如例2:
- 第一个字符串是板子名字(很特定),第二个是芯片名字(比较特定),第三个是芯片系列的名字(比较通用)
//例1
//板子arch/arm/boot/dts/vexpress-v2p-ca9.dts兼容于"arm,vexpress,v2p-ca9"和“arm,vexpress”
compatible = "arm,vexpress,v2p-ca9", "arm, vexpress"
//板子arch/arm/boot/dts/vexpress-v2p-ca5s.dts兼容性如下:
compatible = "arm,vexpress,v2p-ca5s", "arm, vexpress"
//板子arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts兼容性如下:
compatible = "arm,vexpress,v2p-ca15_a7", "arm, vexpress"
//例2
//arch/arm/boot/dts/exymos4210-origen.dts的兼容性:
compatible = "insignal,origen", "samsung,exynos4210", "samsung,exynos4"
//arch/arm/boot/dts/exymos4210-universal_c210.dts的兼容性:
compatible = "insignal,universal_c210", "samsung,exynos4210", "samsung,exynos4"
- ARM Linux针对不同的电路板会建立由MACHINE_START和MACHINE_END包围起来的针对这个machine的一系列callback
- ARM Linux3.x引入Device Tree之后,MACHINE_START变更为DT_MACHINE_START
- DT_MACHINE_START中含有一个.dt_compat成员,用于表明相关的machine与.dts中root结点的compatible属性兼容关系
- 如果Bootloader传递给内核的Device Tree中root结点的compatible属性出现在某machine的.dt_compat表中,相关的machine就与对应的Device Tree匹配,从而引发这一machine的一系列初始化函数被执行。
- ARM Linux3.x引入Device Tree之后,MACHINE_START变更为DT_MACHINE_START
static const char * const v2m_dt_match[] __initconst = {
"arm,vexpress",
"xen,xenvm",
NULL,
};
DT_MACHINE_START(VEXPRESS_DT, "ARM-Versatile Express")
.dt_compat = v2m_dt_match,
.smp = smp_ops(vexpress_smp_ops),
.map_io = v2m_dt_map_io,
.init_early = v2m_dt_init_early,
.init_irq = v2m_dt_init_irq,
.timer = &v2m_dt_timer,
.init_machine = v2m_dt_init,
.handle_irq = gic_handle_irq,
.restart = vexpress_restart,
MACHINE_END
- Linux倡导针对多个SoC、多个电路板的通用DT machine,即一个DT machine的.dt_compat表含多个电路板.dts文件的root结点compatible属性字符串
- 如果这么多电路板的初始化序列不一样,可以透过以下API判断具体的电路板是什么
- 此API判断目前运行的板子或者SOC的兼容性,它匹配的是设备树根节点下的兼容性
- 如果一个兼容包含多个字符串,譬如对于前面介绍的根节点兼容compatible = “samsung, universal_c210”, “samsung,exynos4210”,”samsung,exynos4”的情况,如下表达式都成立
- 如果这么多电路板的初始化序列不一样,可以透过以下API判断具体的电路板是什么
int of_machine_is_compatible(const char *compat)
of_machine_is_compatible("samsung,universal_c210")
of_machine_is_compatible("samsung,exynos4210")
of_machine_is_compatible("samsung,exynos4")
18.2.3 设备节点兼容性
- 在.dts文件的每个设备节点中,都有一个兼容性属性,用于驱动和设备的绑定
- 兼容属性是一个字符串的列表,形式为”,”
- 第一个字符串表征节点代表的确切设备,是个特指
- 第二个字符串表征可兼容的其他设备,涵盖更广的范围
- 例如:在vexpress-v2m.dtsi中Flash节点如下
- 兼容属性的第2个字符串“cfi-flash”明显比第1个字符串“arm,vexpress-flash”涵盖范围更广
- 兼容属性是一个字符串的列表,形式为”,”
flash@0,00000000 {
compatible = "arm,vexpress-flash","cfi-flash";
reg = <0 0x00000000 0x04000000>
<1 0x00000000 0x04000000>;
bank-width = <4>
};
- 使用设备树后,驱动需要与.dts中描述的设备节点进行匹配,从而使驱动的probe()函数执行
- 对于platform_driver而言,需要添加一个OF匹配表,如前文的.dts文件的“acme,a1234-i2c-bus”兼容I2C控制节点的OF匹配表,代码如下:
- 对于i2c和spi从设备而言,同样也可以通过of_match_table添加匹配的.dts中的相关节点的兼容属性
- i2c和spi外设备驱动和设备树中设备节点的兼容属性还有一种弱势匹配方法——“别名”匹配
- 别名就是去掉兼容属性中manufacturer前缀后的部分
//platform设备驱动中的of_match_table
static const struct of_device_id a1234_i2c_of_match[] = {
{ .compatible = "acme,a1234-i2c-bus ", },
{},
};
MODULE_DEVICE_TABLE(of, a1234_i2c_of_match);
static struct platform_driver i2c_a1234_driver = {
.driver = {
.name = "a1234-i2c-bus ",
.owner = THIS_MODULE,
.of_match_table = a1234_i2c_of_match,
},
.probe = i2c_a1234_probe,
.remove = i2c_a1234_remove,
};
module_platform_driver(i2c_a1234_driver);
//i2c和spi设备驱动中的of_match_table
static const struct of_device_id wm8753_of_match[] = {
{ .compatible = "wlf,wm8753", },
{ }
};
MODULE_DEVICE_TABLE(of, wm8753_of_match);
static struct spi_driver wm8753_spi_driver = {
.driver = {
.name = "wm8753",
.owner = THIS_MODULE,
.of_match_table = wm8753_of_match,
},
.probe = wm8753_spi_probe,
.remove = wm8753_spi_remove,
};
static struct i2c_driver wm8753_i2c_driver = {
.driver = {
.name = "wm8753",
.owner = THIS_MODULE,
.of_match_table = wm8753_of_match,
},
.probe = wm8753_i2c_probe,
.remove = wm8753_i2c_remove,
.id_table = wm8753_i2c_id,
};
//spi的别名匹配
static int spi_match_device(struct device *dev, struct device_driver *drv)
{
const struct spi_device *spi = to_spi_device(dev);
const struct spi_driver *sdrv = to_spi_driver(drv);
/* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI */
if (acpi_driver_match_device(dev, drv))
return 1;
if (sdrv->id_table)
return !!spi_match_id(sdrv->id_table, spi);
return strcmp(spi->modalias, drv->name) == 0;
}
static const struct spi_device_id *spi_match_id(const struct spi_device_id *id,
const struct spi_device *sdev)
{
while (id->name[0]) {
if (!strcmp(sdev->modalias, id->name))
return id;
id++;
}
return NULL;
}
- 当一个驱动支持两个或多个设备的时候,这些不同的.dts文件中设备的兼容属性都会写入驱动OF匹配表
- 驱动可以通过bootloader传递给内核设备树中的真正节点的兼容属性以确定究竟是哪一种设备,从而不同设备不同处理
- Linux内核通过如下of_device_is_compatible的API来判断具体的设备是什么
- 除了上面方法,还可以采用在驱动的of_device_id表中填充.data成员的形式
- 例如,arch/arm/mm/cache-12x0.c支持“arm,1210-cache” “arm,p1310-cache” “arm,1220-cache”等多种设备
- 除了上面方法,还可以采用在驱动的of_device_id表中填充.data成员的形式
int of_device_is_compatible(const struct device_node *device, const char *compat);
18.2.4 设备节点及label的命名
-
root结点”/”的cpus子节点下面又包含2个cpu子结点,描述了此machine上的2个CPU,且它们的compatible 属性为”arm,cortex-a9”。
- cpus和cpus的2个cpu子结点的命名所遵循的组织形式为:[@]
- <>中的内容是必选项,[]中的则为可选项
- name:是一个ASCII字符串,用于描述结点对应的设备类型,如3com Ethernet适配器对应的结点name宜为ethernet,而不是3com509
- @unit-address:如果一个结点描述的设备有地址,则应该给出
- 设备的unit-address地址也经常在其对应结点的reg属性中给出
- 多个相同类型设备节点的name可以一样,只要unit-address不同即可,如本例中含有cpu@0、cpu@1以及serial@101f0000与serial@101f2000这样的同名结点
- ePAPR标准给出了节点命名的规范,具体可参考
- cpus和cpus的2个cpu子结点的命名所遵循的组织形式为:[@]
可以给一个设备节点添加label,之后可以通过&label的形式访问这个label,这种引用是通过phandle(pointer handle)进行的
18.2.5 地址编码
- 可寻址的设备使用如下信息在设备树中编码地址信息
- reg的组织形式为reg =
//代码1
reg
#address-cells
#size-cells
//代码2
ranges = <0 0 0x10100000 0x10000 // Chipselect 1, Ethernet
1 0 0x10160000 0x10000 // Chipselect 2, i2c controller
2 0 0x30000000 0x1000000>; // Chipselect 3, NOR Flash
18.2.6 中断连接
- 对于中断控制器而言,它提供如下属性:
- interrupt-controller – 这个属性为空,中断控制器应该加上此属性表明自己的身份
- #interrupt-cells – 与#address-cells 和 #size-cells相似,它表明连接此中断控制器的设备的interrupts属性的cell大小
- 在整个Device Tree中,与中断相关的属性还包括:
- interrupt-parent – 设备结点透过它来指定它所依附的中断控制器的phandle,当节点没有指定interrupt-parent 时,则从父级节点继承
- 对于本例而言,root节点指定了interrupt-parent = <&intc>;其对应于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就有如下文字说明:
- interrupt-parent – 设备结点透过它来指定它所依附的中断控制器的phandle,当节点没有指定interrupt-parent 时,则从父级节点继承
The 1st cell is the interrupt type; 0 for SPI interrupts, 1 for PPI
interrupts.
The 2nd cell contains the interrupt number for the interrupt type.
SPI interrupts are in the range [0-987]. PPI interrupts are in the
range [0-15].
The 3rd cell is the flags, encoded as follows:
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
bits[15:8] PPI interrupt cpu mask. Each bit corresponds to each of
the 8 possible cpus attached to the GIC. A bit set to '1' indicated
the interrupt is wired to that CPU. Only valid for PPI interrupts.
- 一个设备还可能用到多个中断号
- 对于ARM GIC而言,若某设备使用了SPI的168、169号2个中断,而且都是高电平触发,则该设备结点的interrupts属性可定义为:interrupts = <0 168 4>, <0 169 4>;
18.2.7 GPIO、时钟、pinmux连接
- 除了中断以外,在ARM Linux中clock、GPIO、pinmux都可以透过.dts中的结点和属性进行描述
18.3 Device Tree引发的BSP和驱动变更
有了Device Tree后,大量的板级信息都不再需要,譬如过去经常在arch/arm/plat-xxx和arch/arm/mach-xxx实施的如下事情:
18.3.1. 注册platform_device,绑定resource,即内存、IRQ等板级信息。
- 通过设备树后,形如代码1中的platform_device代码都不再需要,其中platform_device会由kernel自动展开
- 这些resource实际来源于.dts中设备结点的reg、interrupts属性
- 典型地,大多数总线都与“simple_bus”兼容,而在SoC对应的machine的.init_machine成员函数中,调用of_platform_bus_probe(NULL, xxx_of_bus_ids, NULL);即可自动展开所有的platform_device
- 譬如,假设我们有个XXX SoC,则可在arch/arm/mach-xxx/的板文件中透过如下方式展开.dts中的设备结点对应的platform_device:如代码2
//代码1
90 static struct resource xxx_resources[] = {
91 [0] = {
92 .start = …,
93 .end = …,
94 .flags = IORESOURCE_MEM,
95 },
96 [1] = {
97 .start = …,
98 .end = …,
99 .flags = IORESOURCE_IRQ,
100 },
101 };
102
103 static struct platform_device xxx_device = {
104 .name = "xxx",
105 .id = -1,
106 .dev = {
107 .platform_data = &xxx_data,
108 },
109 .resource = xxx_resources,
110 .num_resources = ARRAY_SIZE(xxx_resources),
111 };
//代码2
18 static struct of_device_id xxx_of_bus_ids[] __initdata = {
19 { .compatible = "simple-bus", },
20 {},
21 };
22
23 void __init xxx_mach_init(void)
24 {
25 of_platform_bus_probe(NULL, xxx_of_bus_ids, NULL);
26 }
32
33 #ifdef CONFIG_ARCH_XXX
38
39 DT_MACHINE_START(XXX_DT, "Generic XXX (Flattened Device Tree)")
41 …
45 .init_machine = xxx_mach_init,
46 …
49 MACHINE_END
50 #endif
18.3.2 注册i2c_board_info,指定IRQ等板级信息。
- 形如代码1之类的i2c_board_info代码,目前不再需要出现
//代码1
145 static struct i2c_board_info __initdata afeb9260_i2c_devices[] = {
146 {
147 I2C_BOARD_INFO("tlv320aic23", 0x1a),
148 }, {
149 I2C_BOARD_INFO("fm3130", 0x68),
150 }, {
151 I2C_BOARD_INFO("24c64", 0x50),
152 },
153 };
- 现在只需要把tlv320aic23、fm3130、24c64这些设备结点填充作为相应的I2C controller结点的子结点即可,类似于前面的代码2
- Device Tree中的I2C client会透过I2C host驱动的probe()函数中调用of_i2c_register_devices(&i2c_dev->adapter);被自动展开。
i2c@1,0 {
compatible = "acme,a1234-i2c-bus";
…
rtc@58 {
compatible = "maxim,ds1338";
reg = <58>;
interrupts = < 7 3 >;
};
};
18.3.3 注册spi_board_info,指定IRQ等板级信息。
- 如下的spi_board_info代码,目前不再需要出现
79 static struct spi_board_info afeb9260_spi_devices[] = {
80 { /* DataFlash chip */
81 .modalias = "mtd_dataflash",
82 .chip_select = 1,
83 .max_speed_hz = 15 * 1000 * 1000,
84 .bus_num = 0,
85 },
86 };
- 与I2C类似,现在只需要把mtd_dataflash之类的结点,作为SPI控制器的子结点即可,SPI host驱动的probe函数透过spi_register_master()注册master的时候,会自动展开依附于它的slave。
18.3.4 多个针对不同电路板的machine,以及相关的callback。
- 过去,ARM Linux针对不同的电路板会建立由MACHINE_START和MACHINE_END包围起来的针对这个machine的一系列callback,譬如:
[cpp] view plain copy
373 MACHINE_START(VEXPRESS, “ARM-Versatile Express”)
374 .atag_offset = 0x100,
375 .smp = smp_ops(vexpress_smp_ops),
376 .map_io = v2m_map_io,
377 .init_early = v2m_init_early,
378 .init_irq = v2m_init_irq,
379 .timer = &v2m_timer,
380 .handle_irq = gic_handle_irq,
381 .init_machine = v2m_init,
382 .restart = vexpress_restart,
383 MACHINE_END - Device Tree之后,MACHINE_START变更为DT_MACHINE_START
18.3.5 设备与驱动的匹配方式
- 在上面介绍过
18.3.6 设备平台数据属性化
18.4 常用的OF API
在Linux的BSP和驱动代码中,还经常会使用到Linux中一组Device Tree的API,这些API通常被冠以of_前缀,它们的实现代码位于内核的drivers/of目录。
18.4.1 寻找节点
- 根据compatible属性,获得设备结点
- 遍历Device Tree中所有的设备结点,看看哪个结点的类型、compatible属性与本函数的输入参数匹配
- 大多数情况下,from、type为NULL。
struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible);
18.4.2 读取属性
int of_property_read_u8_array(const struct device_node *np, const char *propname, u8 *out_values, size_t sz);
int of_property_read_u16_array(const struct device_node *np, const char *propname, u16 *out_values, size_t sz);
int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz);
int of_property_read_u64(const struct device_node *np, const char *propname, u64 *out_value);
- 读取设备结点np的属性名为propname,类型为8、16、32、64位整型数组的属性
- 对于32位处理器来讲,最常用的是of_property_read_u32_array()
- 如在arch/arm/mm/cache-l2x0.c中,透过如下语句读取L2 cache的”arm,data-latency”属性:
- 对于32位处理器来讲,最常用的是of_property_read_u32_array()
534 of_property_read_u32_array(np, "arm,data-latency",
535 data, ARRAY_SIZE(data));
- 在arch/arm/boot/dts/vexpress-v2p-ca9.dts中,含有”arm,data-latency”属性的L2 cache结点如下:
137 L2: cache-controller@1e00a000 {
138 compatible = "arm,pl310-cache";
139 reg = <0x1e00a000 0x1000>;
140 interrupts = <0 43 4>;
141 cache-level = <2>;
142 arm,data-latency = <1 1 1>;
143 arm,tag-latency = <1 1 1>;
144 }
有些情况下,整形属性的长度可能为1,于是内核为了方便调用者,又在上述API的基础上封装出了更加简单的读单一整形属性的API,它们为int of_property_read_u8()、of_property_read_u16()等,实现于include/linux/of.h:
513 static inline int of_property_read_u8(const struct device_node *np,
514 const char *propname,
515 u8 *out_value)
516 {
517 return of_property_read_u8_array(np, propname, out_value, 1);
518 }
519
520 static inline int of_property_read_u16(const struct device_node *np,
521 const char *propname,
522 u16 *out_value)
523 {
524 return of_property_read_u16_array(np, propname, out_value, 1);
525 }
526
527 static inline int of_property_read_u32(const struct device_node *np,
528 const char *propname,
529 u32 *out_value)
530 {
531 return of_property_read_u32_array(np, propname, out_value, 1);
532 }
处整形属性外,字符串属性也比较常用,其对应的API包括:
int of_property_read_string(struct device_node *np, const char
*propname, const char **out_string);
int of_property_read_string_index(struct device_node *np, const char
*propname, int index, const char **output);
前者读取字符串属性,后者读取字符串数组属性中的第index个字符串。如drivers/clk/clk.c中的of_clk_get_parent_name()透过of_property_read_string_index()遍历clkspec结点的所有”clock-output-names”字符串数组属性。
1759 const char *of_clk_get_parent_name(struct device_node *np, int index)
1760 {
1761 struct of_phandle_args clkspec;
1762 const char *clk_name;
1763 int rc;
1764
1765 if (index < 0)
1766 return NULL;
1767
1768 rc = of_parse_phandle_with_args(np, "clocks", "#clock-cells", index,
1769 &clkspec);
1770 if (rc)
1771 return NULL;
1772
1773 if (of_property_read_string_index(clkspec.np, "clock-output-names",
1774 clkspec.args_count ? clkspec.args[0] : 0,
1775 &clk_name) < 0)
1776 clk_name = clkspec.np->name;
1777
1778 of_node_put(clkspec.np);
1779 return clk_name;
1780 }
1781 EXPORT_SYMBOL_GPL(of_clk_get_parent_name);
处整形外、字符串以外的最常用属性类就是布尔类型,其对应的API如下
static inline bool of_property_read_bool(const struct device_node *np, const char *propname);
如果设备结点np含有propname属性,则返回true,否则返回false。一般用于检查空属性是否存在。
18.4.3 内存映射
void __iomem *of_iomap(struct device_node *node, int index);
通过设备结点直接进行设备内存区间的 ioremap(),index是内存段的索引。若设备结点的reg属性有多段,可通过index标示要ioremap的是哪一段,只有1段的情况,index为0。采用Device Tree后,大量的设备驱动通过of_iomap()进行映射,而不再通过传统的ioremap。
unsigned int irq_of_parse_and_map(struct device_node *dev, int index);
透过Device Tree或者设备的中断号,实际上是从.dts中的interrupts属性解析出中断号。若设备使用了多个中断,index指定中断的索引号。
还有一些OF API,这里不一一列举,具体可参考include/linux/of.h头文件。