Linux Kernel DT(Device Tree)
Posted sky
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux Kernel DT(Device Tree)相关的知识,希望对你有一定的参考价值。
转自:https://www.jianshu.com/p/923b380366bb
之前在使用的3.0.8版本内核还没有使用上DT,而最近在研发使用的3.10.37版本内核已使用上了DT,瞬间感觉自己的知识体系更新慢了,查了资料发现3.x版本的内核已经支持DT了,为何ARM也要使用上DT呢?
在旧版本的ARM Linux内核里,我们习惯上会去arch/arm/mach-XXX/目录下进行一些板载级设备配置,尤其在board-YYY.c文件里使用platform_add_devices()等函数去注册一堆硬件设备以及板级初始化操作,还有如下宏:
MACHINE_START(project name, "board name")
.boot_params = PLAT_PHYS_OFFSET + 0x800,
.fixup = XXX_fixup,
.reserve = &XXX_reserve,
.map_io = XXX_map_io,
.init_irq = XXX_init_irq,
.timer = &XXX_timer,
.init_machine = XXX_init,
MACHINE_END
其中的XXX_init函数里就会调用platform_add_devices()。
以及arch/arm/plat-XXX目录下也有一堆平台级的操作,一般在进行移植工作的时候,就是修改了上面的board-YYY.c文件,调试好了各种Clock之后,剩下的就是设备驱动程序了。这些处理往往在所有ARM平台里有很多的相同操作和共同定义,而这些往往存在了大量的重复编码工作,故而Linux内核的开发人员和ARM的相关人员引入了DT来改善该问题,相关的历史有如下引用内容:
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 中):
? CPU 的数量和类别
? 内存基地址和大小
? 总线和桥
? 外设连接
? 中断控制器和中断使用情况
? GPIO 控制器和GPIO 使用情况
? Clock 控制器和Clock 使用情况
它基本上就是画一棵电路板上CPU、总线、设备组成的树,Bootloader 会将这棵树传递给内核,然后内核可以识别这棵树,并根据它展开出Linux 内核中的platform_device、i2c_client、spi_device 等设备,而这些设备用到的内存、IRQ 等资源,也被传递给了内核,内核会将这些资源绑定给展开的相应的设备。
了解完相关历史后,我们接下来分如下几个方面了解ARM Linux中的DT:
使用和不使用DT对bootloader和kernel的影响
关于DT
kernel中的DT
一.使用和不使用DT对bootloader和kernel的影响
1.不使用DT
不使用DT时,kernel包含了硬件的完整描述信息,bootloader加载单独的一个二进制文件(kernel镜像文件uImage或zImage)并执行它,bootloader通过寄存器r2传递ATAGS(为一些附加信息,如RAM大小和地址、cmdline等)给kernel,通过寄存器r1传递一个机器类型(machine type,用于告诉内核将启动哪一款板卡)整数给kernel。有如下映射:
这时候,在U-boot命令行里执行bootm 命令可以启动kernel。
2.使用DT
使用DT时,kernel包含的硬件完整打桩信息被提取为一个二进制文件DTB(device tree blob)文件,bootloader则需要加载kernel镜像(uImage或zImage)以及DTB(arch/arm/boot/dts/目录下的DTS文件<一个板卡一个dts文件>通过DTC编译成DTB文件),bootloader通过寄存器r2传递DTB文件(该文件也包含了RAM信息、cmdline等信息)所在地址给kernel,而原先传递板卡类型整数的r1则不需要再关注了,相应的映射如下:
这时候,在U-boot里使用命令bootm - 来启动kernel。
3.bootloader对DT的支持
Uboot的主线代码从v1.1.3开始就支持DT了,其对ARM的支持和kernel对Device Tree的支持是同期完成的,在Uboot中需要在config文件中加入#define CONFIG_OF_LIBFDT配置项即可,当我们将DTB文件在Uboot里加载到内存中后,通过fdt addr 0xnnnnnnnn命令来设置DTB文件对应地址,这样就可以使用fdt resize、fdt print等命令对DTB文件进行操作了。对于ARM,使用bootz kernel_addr initrd_addr dtb_addr命令来启动kernel,dtb_addr作为bootz或bootm最后一个参数,第一个参数为内核镜像的地址,第二个参数为initrd的地址,如不存在,使用-代替(看完这句话,一的2中的命令就能理解了)。
4.kernel的DT兼容引导模式
在实际情况下,存在部分平台过旧,方案厂无法提供新版的bootloader,而原有的bootloader不支持DT时,还好kernel有兼容机制:
当设置CONFIG_ARM_APPENDED_DTB为y时,它表示我们使用kernel时,需要在kernel镜像后面查找DTB信息(即kernel镜像后紧挨着DTB),而Makefile也没有相应的规则去生成相应格式的kernel镜像,此时,我们需要执行如下命令手动制作相应的镜像:
cat arch/arm/boot/zImage arch/arm/boot/dts/myboard.dtb > my-zImage
mkimage ... –d my-zImage my-uImage
而当设置CONFIG_ARM_ATAG_DTB_COMPAT为y时,它表示kernel将从bootloader获取到ATAGS信息,并更新DT文件使用这些信息。
二.关于DT
首先,先看下一个基本的DT语法格式图示:
上图中所保存成的一个.dts文件并不会有实质的功能,仅仅是一个Device Tree源文件结构的呈现,由图可见,一个.dts文件包含一个root结点"/",root结点下面有一系列子结点,上图中有node@0和node@1,其中node@0下面还有两个子结点child-node@0和child-node@1,node@1下面有child-node@0子结点,而结点中又有一系列的属性,如属性来空:an-empty-property,为字符串:a-string-property,为字符串数组:a-string-list-property,为二进制:a-byte-data-property,为Cells(由u32整数组成):a-cell-property、second-child-property,为引用:a-reference-to-something。还有别名node1,实际上是node@1结点,如果引用时,则需要完整路径/node@1,而使用别名可以省掉这绝对路径的一长串字符。
接下来,拿一个实例来说明下一个平台于DTS的配置:
假如我们有如下配置的一台机器:
1个双核ARM Cortex-A9 32位处理器
ARM的local bus上的内在映射区域分布如下控制器:
2个串口(分别位于0x101F1000和0x101F2000)
GPIO控制器(0x101F3000)
SPI控制器(0x10170000)
中断控制器(0x10160000)
一个external bus桥,桥上连接如下设备:
SMC SMC91111 Ethernet(0x10100000)
I2C控制器(0x10160000),上面接了如下设备:
Maxim DS1338 RTC(I2C地址0x58)
64MB Nor Flash(0x30000000)
上述配置对应的.dts文件内容如下:
/ {
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>;
};
};
};
上面的.dts文件中,root结点"/"的compatible属性(compatible = "acme,coyotes-revenge";)定义了系统的名字,其组织形式为:,。Linux kernel通过root结点的该属性就可以判断需要启动的是什么machine。
在.dts文件中的每个设备,都有一个compatible属性,用于驱动与设备间的绑定。compatible属性是一个字符串列表,该列表第一个字符串表示了结点所代表的确切设备,其后的字符串代表可兼容的设备,在上面的.dts文件中有如下内容:
compatible = “samsung,k8f1315ebm”, “cfi-flash”;
reg = <2 0 0x4000000>;
};
其中,compatible属性的第一个字符串”samsung,k8f1315ebm”表示了该配置项是对samsung的k8f1315ebm这款Nor Flash的支持,第二个字符串”cfi-flash”表示可被兼容的设备型号(省略了厂商信息,表示可用的范围更广)。
接下来root结点”/”的cpus子结点下包含了2个cpu子结点,描述了该machine上的双核CPU,并且这两个子结点的compatible属性都为”arm,cortex-a9”。注意这两个子结点的命名都遵循的组织形式为:[@],其中<>中的内容是必选项,而[]中的则为可选项。name是一个ASCII字符串,用于描述结点对应的设备类型,如3com Ethernet PHY对应的结点name为ethernet,而不是3com509。如果一结点描述的设备有地址,则应给出相应的@unit-address。多个相同类型的设备结点的name可以一样,只需相应的unit-address不同即可,如上述.dts文件中所示。设备的unit-address地址通常也在其对应结点的reg属性中给出。
可寻址的设备使用reg、#address-cells、#size-cells三属性来确定其在DT中的编址信息,其中reg的组织形式为:reg=,这里每一组address length表明了该设备使用的一个地址范围。address和length字段是可变长的,address为1个或多个cell(32位的整形),而length则为cell列表或空,父结点的#address-cells和#size-cells决定了子结点的reg属性的address和length字段的长度。在上面的.dts中,root结点的#address-cells=<1>;和#size-cells=<1>;决定了serial、gpio、spi等结点的address和length字段的长度都为1。cpus结点的#address-cells=<1>;和#size-cells=<0>;决定了2个cpu子结点的address长度为1,而length为空,即2个cpu的reg分别为reg=<0>;和reg=<1>;。external-bus结点的#address-cells=<2>;和#size-cells=<1>;决定了其子结点ethernet、i2c、flash的reg字段为reg=<0 0 0x1000>;、reg=<1 0 0x1000>;和reg=<2 0 0x4000000>;,这三个reg的第一个值分别为0、1、2,是对应的片选,第二个值都为0,表示相应片选的基地址,第三个值分别为0x1000、0x1000、0x4000000为length。特别要注意的是i2c结点中定义的#address-cells=<1>;和#size-cells=<0>;作用到相应总结上的RTC设备的address字段则为0x58,是该设备的i2c地址。
对于root结点的子结点,其address区域直接位于CPU的memory区域,均为CPU的视图范围,但是,经过总线桥后的address一般需要进行转换才能被CPU的memory区域映射到,从上面的.dts文件中可看到,external-bus的ranges属性定义了经过external-bus桥后的地址范围如何映射到CPU的memory区域,如下所示:
ranges = <0 0 0x10100000 0x10000 // Chipselect 1, Ethernet
1 0 0x10160000 0x10000 // Chipselect 2, i2c controller
2 0 0x30000000 0x1000000>; // Chipselect 3, NOR Flash
ranges是地址转换表,其中第个项目是一个子地址、父地址及其空间大小的映射。映射表中的子地址、父地址分别采用子地址空间的#address-cells和父地址空间的#address-cells大小。对于这儿的ranges,子地址空间的#address-cells为2,父地址空间的#address-cells为1,故而0 0 0x10100000 0x10000的前两项(cell)为external-bus片选0上偏移0,第3项为external-bus片选0上偏移0的地址空间被映射到CPU的0x10100000位置上,第4项为映射的大小为0x10000。后面的两组片选同样理解。
对于中断控制器,DT提供了如下属性:
interrupt-controller——这个属性为空,中断控制器使用该属性表明身份;
#interrupt-cells——和#address-cells和#size-cells相似,表明使用该中断控制器的设备的interrupts属性的cell大小;
interrupt-parent——设备结点通过它来指定其所依附的中断控制器的phandle,当结点没指定interrupt-parent时,则从父结点继续。上面的.dts中,root结点指定了interrupt-parent=<&intc>;其对应于intc:interrupt-controller@10140000,而root结点的子结点没有指定interrupt-parent,故而都继续了intc,即位于0x10140000的中断控制器。
interrupts——用到了中断的设备结点通过它来指定中断号、触发方式等,具体该属性含有多少个cell,由其所依附的中断控制器结点的#interrupt-cells属性决定,而具体每个cell的含义,一般由驱动的实现决定,其会在DT的binding文档中说明,如ARM GIC中断控制器,#interrupt-cells为3,3个cell的具体含义可查阅Documentation/devicetree/bindings/arm/gic.txt文件,有如下说明:
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两个中断,并且都是高电平触发,那么该设备结点的interrupts属性可定义为:interrupts=<0 168 4>,<0 169 4>;。除了中断外,ARM Linux中clock、GPIO、pinmux都可通过.dts来描述,而pinmux/pinctl与对应的平台关系较大。
三.kernel中的DT
1.DTB、DTS和DTC
在ARM Linux Kernel中,所有的DTS(Device Tree Source)文件存放在arch/arm/boot/dts目录下,该目录下后缀为.dts的文件为板级定义,而.dtsi为SOC级定义,是被包含的文件,在.dts文件中会使用/include/ “XXX.dtsi”或#include “XXX.dtsi”这样的语句放在文件最开始位置,将相关文件包含进来。既然.dts可以包含.dtsi,如果碰到两文件中有定义了同一结点,那么会合并两文件中的所有属性,而当为同一属性时,则以.dts文件中的为准,有如下例图所示:
对于DTC(Device Tree Compiler)会将.dts文件编译为.dtb文件,该工具的源码在scripts/dtc目录下,在该目录下的Makefile文件中有hostprogs-y := dtc一句,确保在编译时会将该dtc编译为主机工具。
对于DTB(Device Tree Blob)文件由DTC编译生成,是在bootloader中被加载和在kernel引导时被解析的二进制文件。
2.kernel对DT支持做的改变
使用DT后,以往所使用的大量板级信息都不再需要的,例如在arch/arm/plat-xxx和arch/arm/mach-xxx曾经经常实施的操作:
a.注册platform_device、绑定resource,即内存和IRQ等板级信息
使用DT后,形如
static struct resource xxx_resources[] = {
[0] = {
.start = …,
.end = …,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = …,
.end = …,
.flags = IORESOURCE_IRQ,
},
};
static struct platform_device xxx_device = {
.name = “xxx”,
.id = -1,
.dev = {
.platform_data = &xxx_data,
},
.resource = xxx_resources,
.num_resources = ARRAY_SIZE(xxx_resources),
};
之类的platform_device代码都不再需要了,其中platform_device 会由kernel自动展开,其中这些resource来自.dts的设备结点reg、interruptes属性。
比较典型的,大多数总结与“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:
static struct of_device_id xxx_of_bus_ids[] __initdata = {
{ .compatible = “simple-bus”, },
{},
};
void __init xxx_mach_init(void){
of_platform_bus_probe(NULL, xxx_of_bus_ids, NULL);
}
#ifdef CONFIG_ARCH_XXX
DT_MACHINE_START(XXX_DT, “Generic XXX (Flattened Device Tree)”)
…
.init_machine = xxx_mach_init,
…
MACHINE_END
#endif
b.注册i2c_board_info,指定IRQ等板级信息
形如:
static struct i2c_board_info __initdata afeb9260_i2c_devices[] = {
{
I2C_BOARD_INFO(“tlv320aic23”, 0x1a),
}, {
I2C_BOARD_INFO(“fm3130”, 0x68),
}, {
I2C_BOARD_INFO(“24c64”, 0x50),
},
};
之类的代码,现在也不需要了,只需要将tlv320aic23、fm3130和24c64之类的设备结点填充到相应的I2C Controller结点的子结点中,如上面的.dts中所示。DT的I2C Client会通过I2C Host驱动的probe()函数中调用of_i2c_register_devices(&i2c_dev->adapter);被自动展开使用。
c.注册spi_board_info,指定IRQ等板级信息
形如:
static struct spi_board_info afeb9260_spi_devices[] = {
{ /* DataFlash chip /
.modalias = “mtd_dataflash”,
.chip_select = 1,
.max_speed_hz = 15 * 1000 * 1000,
.bus_num = 0,
},
};
之类的代码也不再需要了,只需将mtd_dataflash之类结点作为SPI控制器的子结点即可,SPI Host驱动的probe函数通过spi_register_master()注册master的时候,会自动展开其slave。
d.指定电路板的machine及相关callback
以前,ARM Linux 针对不同的电路板会建立由MACHINE_START 和MACHINE_END 包围起来的针对这个machine 的一系列callback,如:
MACHINE_START(VEXPRESS, “ARM-Versatile Express”)
.atag_offset = 0x100,
.smp = smp_ops(vexpress_smp_ops),
.map_io = v2m_map_io,
.init_early = v2m_init_early,
.init_irq = v2m_init_irq,
.timer = &v2m_timer,
.handle_irq = gic_handle_irq,
.init_machine = v2m_init,
.restart = vexpress_restart,
MACHINE_END
Uboot在启动kernel时会将MACHINE_ID放在r1中,Linux Kernel启动时会匹配传过来的MACHINE_ID,将该值与MACHINE_START处声明的MACHINE_ID比对,如匹配则会执行相应machine的一系列初始化函数。
使用DT后,MACHINE_START变为DT_MACHINE_START,其中多了一个.dt_compat成员,用于指定该machine与.dts中root结点compatible属性兼容关系。一旦bootloader传递给kernel的DTB中root结点compatible属性出现在某machine的.dt_compat表中,则匹配上了,从而执行相应的初始化函数。如:
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属性字符串,之后,如果不同的电路板的初始化序列不一样时,可通过of_machine_is_compatible() API来判断。例如arch/arm/mach-exynos/mach-exynos5-dt.c 的EXYNOS5_DT machine 同时兼容”samsung,exynos5250”和”samsung,exynos5440”:
static char const *exynos5_dt_compat[] __initdata = {
“samsung,exynos5250”,
“samsung,exynos5440”,
NULL
};
DT_MACHINE_START(EXYNOS5_DT, “SAMSUNG EXYNOS5 (Flattened Device Tree)”)
/
Maintainer: Kukjin Kim /
.init_irq = exynos5_init_irq,
.smp = smp_ops(exynos_smp_ops),
.map_io = exynos5_dt_map_io,
.handle_irq = gic_handle_irq,
.init_machine = exynos5_dt_machine_init,
.init_late = exynos_init_late,
.timer = &exynos4_timer,
.dt_compat = exynos5_dt_compat,
.restart = exynos5_restart,
.reserve = exynos5_reserve,
MACHINE_END
其.init_machine成员函数中有针对不同machine进行分支处理:
static void __init exynos5_dt_machine_init(void)
{
…
if (of_machine_is_compatible(“samsung,exynos5250”))
of_platform_populate(NULL, of_default_bus_match_table, exynos5250_auxdata_lookup, NULL);
else if (of_machine_is_compatible(“samsung,exynos5440”))
of_platform_populate(NULL, of_default_bus_match_table, exynos5440_auxdata_lookup, NULL);
}
使用DT后,驱动需要与.dts中描述的设备结点匹配,才会引发驱动的probe()函数执行。对于platform_driver,需要添加一个OF匹配表,如上面的.dts文件的”acme,a1234-i2c-bus”兼容I2C 控制器结点的OF 匹配表为:
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添加匹配的.dts中相关结点的compatible属性,如sound/soc/codecs/wm8753.c中有:
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,
};
这需要注意的是,I2C和SPI外设驱动和DT中的compatible属性还有一种弱匹配方法,即别名匹配。compatible属性的组织形式为:,,别名为去掉manufacturer所剩下的model。对此,在drivers/spi/spi.c文件中的spi_match_device()函数中有相应处理,如果别名在设备spi_driver的id_table里或与spi_driver的name字段相同,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;
}
3.kernel中获取DT内容的API
在Linux Kernel中,会使用到与DT相关的API,通常以of_为前缀,其实现在drivers/of目录下,常用的API有:
int of_device_is_compatible(const struct device_node *device,const char *compat);
用于判断设备结点的compatible属性是否包含compat指定的字符串。当一个驱动支持2个以上设备时,这些不同的.dts文件中设备的compatible属性都会在驱动OF匹配表中。
struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible);
根据compatible属性获得设备结点。遍历DT中所有设备结点,确认哪个结点的类型、compatible属性与本函数的输入参数匹配,很多时候,from和type为NULL。
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位整型数组的属性。有时候整型属性的长度可能为1,故而双有如下API,其在include/linux/of.h文件中定义如下:
static inline int of_property_read_u8(const struct device_node *np, const char *propname, u8 *out_value)
{
return of_property_read_u8_array(np, propname, out_value, 1);
}
static inline int of_property_read_u16(const struct device_node *np, const char *propname, u16 *out_value)
{
return of_property_read_u16_array(np, propname, out_value, 1);
}
static inline int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value)
{
return of_property_read_u32_array(np, propname, out_value, 1);
}
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个字符串。
static inline bool of_property_read_bool(const struct device_node *np, const char *propname);
如果设备结点np含有propname属性,则返回true,否用返回false。一般用于检查是否存在空属性。
void __iomem *of_iomap(struct device_node *node, int index);
通过设备结点直接进行设备内存区间的ioremap(),index是内存段的索引。如果设备结点的reg属性有多段,可通过index来标示取ioremap的哪一段,只为1段的情况下,index才为0。使用DT后,设备驱动使用of_iomap()替代ioremap()来进行映射。
unsigned int irq_of_parse_and_map(struct device_node *dev, int index);
通过DT或设备的中断号,实际是从.dts中的interrupts属性解析出中断号,如设备使用多个中断,index指定中断的索引号。
还有一些驱动端在使用DT后获取属性时的API范例:
a.获取时钟的引用
clocks属性
s->clk=clk_get(&pdev->dev, NULL);
b.获取I/O寄存器资源
reg属性
r=platform_get_resource(pdev, IORESOURCE_MEM, 0);
c.获取中断
interrupts属性
s->irq=platform_get_irq(pdev, 0);
d.获取DMA channel
dmas属性
s->rx_dma_chan=dma_request_slave_channel(s->dev, “rx”);
s->tx_dma_chan=dma_request_slave_channel(s->dev, “tx”);
四.参考资料
1.ARM Device Tree设备树.pdf
2.petazzoni-device-tree-dummies.pdf
3.Power_ePAPR_APPROVED_v1.1.pdf
4.http://devicetree.org/Device_Tree_Usage
5.http://www.wowotech.net/linux_kenrel/why-dt.html
6.http://www.wowotech.net/linux_kenrel/dt_basic_concept.html
7.http://www.wowotech.net/linux_kenrel/dt-code-analysis.html
注:本文部分内容摘自“参考资料”,“参考资料”的文档会在后续文章中上传。
作者:网路元素
链接:https://www.jianshu.com/p/923b380366bb
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
以上是关于Linux Kernel DT(Device Tree)的主要内容,如果未能解决你的问题,请参考以下文章