Linux Kernel DT(Device Tree)

作者: 网路元素 | 来源:发表于2018-04-08 18:40 被阅读513次

    之前在使用的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

    注:本文部分内容摘自“参考资料”,“参考资料”的文档会在后续文章中上传。

    相关文章

      网友评论

        本文标题:Linux Kernel DT(Device Tree)

        本文链接:https://www.haomeiwen.com/subject/tslshftx.html