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@0node@1,其中node@0下面还有两个子结点child-node@0child-node@1node@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文件中有如下内容:

 flash@2,0 {

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)的主要内容,如果未能解决你的问题,请参考以下文章

linux kernel 何时上报uevent

Device Tree

03day03misc_device

linux :vmware kernel update导致vmware无法打开,解决

图解Kernel Device Tree(设备树)的使用

《Linux Device Drivers》第八章 分配内存——note