ARM Linux 3.x的设备树(Device Tree)

时间:2022-05-25 13:41:53

1. ARM Device Tree起源

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等资源,也被传递给了内核,内 核会将这些资源绑定给展开的相应的设备.

2. Device Tree组成和结构

整个Device Tree牵涉面比较广,即增加了新的用于描述设备硬件信息的文本格式,又增加了编译这一文本的工具,同时Bootloader也需要支持将编译后的Device Tree传递给Linux内核.

DTS (device tree source)

.dts 文件是一种ASCII 文本格式的Device Tree描述,此文本格式非常人性化,适合人类的阅读习惯.基本上,在ARM Linux在,一个.dts文件对应一个ARM的machine,一般放置在内核的arch/arm/boot/dts/目录.由于一个SoC可能对应多个machine(一个SoC可以对应多个产品和电路板),势必这些.dts文件需包含许多共同的部分,Linux内核为了简化,把SoC公用的部分或者多个machine共同的部分一般提炼为.dtsi,类似于C语言的头文件.其他的machine对应的.dts就include这个.dtsi.譬如,对于VEXPRESS而言,vexpress-v2m.dtsi就被vexpress-v2p-ca9.dts所引用,vexpress-v2p-ca9.dts有如下一行:

/include/ "vexpress-v2m.dtsi"

当然,和C语言的头文件类似,.dtsi也可以include其他的.dtsi,譬如几乎所有的ARM SoC的.dtsi都引用了skeleton.dtsi.

.dts(或者其include的.dtsi)基本元素即为前文所述的结点和属性:

/ {

    node1 {

        a-string-property = "A string";

        a-string-list-property = "first string", "second string";

        a-byte-data-property = [0x01 0x23 0x34 0x56];

        child-node1 {

            first-child-property;

            second-child-property = <>;

            a-string-property = "Hello, world";

        };

        child-node2 {

        };

    };

    node2 {

        an-empty-property;

        a-cell-property = <   >; /* each number (cell) is a uint32 */

        child-node1 {

        };

    };

};

上述.dts文件并没有什么真实的用途,但它基本表征了一个Device Tree源文件的结构:

  • 1个root结点"/";
  • root结点下面含一系列子结点,本例中为"node1" 和 "node2";
  • 结点"node1"下又含有一系列子结点,本例中为"child-node1" 和 "child-node2";
  • 各结点都有一系列属性.属性都是简单的key-value对,其中value可以是空的或包含任意的byte流.

  1. 属性为空,如node2的an-empty-property;

  2. 为双引号包含的字符串,如a-string-property = "A string";

  3. 为字符串数组,如a-string-list-property = "first string", "second string";

  4. 为Cells(32位无符号整数),如second-child-property = <1>;

  5. 为二进制数,如a-byte-data-property = [0x01 0x23 0x34 0x56];

  6. 为混合数据(不同类型数据用逗号隔开),如mixed-property = "a string", [0x01 0x02 0x03 0x04], <0xFF01 412 0x12341283>;

下面以一个最简单的machine为例来看如何写一个.dts文件.假设此machine的配置如下:

1个双核ARM Cortex-A9 32位处理器;

ARM 的local bus上的内存映射区域分布了2个串口(分别位于0x101F1000 和 0x101F2000)、GPIO控制器(位于0x101F3000)、SPI控制器(位于0x10170000)、中断控制器(位于 0x10140000)和一个external bus桥;

External bus桥上又连接了SMC SMC91111 Ethernet(位于0x10100000)、I2C控制器(位于0x10160000)、64MB NOR Flash(位于0x30000000);

External bus桥上连接的I2C控制器所对应的I2C总线上又连接了Maxim DS1338实时钟(I2C地址为0x58).

其对应的.dts文件为:

/ {

    compatible = "acme,coyotes-revenge";

    #address-cells = <>;

    #size-cells = <>;

    interrupt-parent = <&intc>;

    cpus {

        #address-cells = <>;

        #size-cells = <>;

        cpu@ {

            compatible = "arm,cortex-a9";

            reg = <>;

        };

        cpu@ {

            compatible = "arm,cortex-a9";

            reg = <>;

        };

    };

    serial@101f0000 {

        compatible = "arm,pl011";

        reg = <0x101f0000 0x1000 >;

        interrupts = <   >;

    };

    serial@101f2000 {

        compatible = "arm,pl011";

        reg = <0x101f2000 0x1000 >;

        interrupts = <   >;

    };

    gpio@101f3000 {

        compatible = "arm,pl061";

        reg = <0x101f3000 0x1000

               0x101f4000 0x0010>;

        interrupts = <   >;

    };

    intc: interrupt-controller@ {

        compatible = "arm,pl190";

        reg = <0x10140000 0x1000 >;

        interrupt-controller;

        #interrupt-cells = <>;

    };

    spi@ {

        compatible = "arm,pl022";

        reg = <0x10115000 0x1000 >;

        interrupts = <   >;

    };

    external-bus {

        #address-cells = <>

        #size-cells = <>;

        ranges = <   0x10100000   0x10000     // Chipselect 1, Ethernet

                     0x10160000   0x10000     // Chipselect 2, i2c controller

                     0x30000000   0x1000000>; // Chipselect 3, NOR Flash

        ethernet@, {

            compatible = "smc,smc91c111";

            reg = <  0x1000>;

            interrupts = <   >;

        };

        i2c@, {

            compatible = "acme,a1234-i2c-bus";

            #address-cells = <>;

            #size-cells = <>;

            reg = <  0x1000>;

            interrupts = <   >;

            rtc@ {

                compatible = "maxim,ds1338";

                reg = <>;

                interrupts = <   >;

            };

        };

        flash@, {

            compatible = "samsung,k8f1315ebm", "cfi-flash";

            reg = <  0x4000000>;

        };

    };

};

上述.dts文件中,root结点"/"的compatible 属性compatible = "acme,coyotes-revenge";定义了系统的名称,它的组织形式为:<manufacturer>,<model>.Linux内核透过root结点"/"的compatible属性即可判断它启动的是什么machine.

在.dts文件的每个设备,都有一个compatible 属性,compatible属性用户驱动和设备的绑定.compatible 属性是一个字符串的列表,列表中的第一个字符串表征了结点代表的确切设备,形式 为"<manufacturer>,<model>",其后的字符串表征可兼容的其他设备.可以说前面的是特指,后面的则涵盖更广的范围.如在arch/arm/boot/dts/vexpress-v2m.dtsi中的Flash结点:

flash@, {

     compatible = "arm,vexpress-flash", "cfi-flash";

     reg = < 0x00000000 0x04000000>,

     < 0x00000000 0x04000000>;

     bank-width = <>;

 };

compatible属性的第2个字符串"cfi-flash"明显比第1个字符串"arm,vexpress-flash"涵盖的范围更广.

再比如,Freescale MPC8349 SoC含一个串口设备,它实现了国家半导体(National Semiconductor)的ns16550 寄存器接口.则MPC8349串口设备的compatible属性为compatible = "fsl,mpc8349-uart", "ns16550".其中,fsl,mpc8349-uart指代了确切的设备,ns16550代表该设备与National Semiconductor 的16550 UART保持了寄存器兼容.

接下来root结点"/"的cpus子结点下面又包含2个cpu子结点,描述了此machine上的2个CPU,并且二者的compatible 属性为"arm,cortex-a9".

注意cpus和cpus的2个cpu子结点的命名,节点的命名应该取类型名,而不是IC名.它们遵循的组织形式为:<name>[@<unit- address>],<>中的内容是必选项,[]中的则为可选项.name是一个ASCII字符串,用于描述结点对应的设备类型,如 3com Ethernet适配器对应的结点name宜为ethernet,而不是3com509.如果一个结点描述的设备有地址,则应该给出@unit- address.多个相同类型设备结点的name可以一样,只要unit-address不同即可,如本例中含有cpu@0、cpu@1以及 serial@101f0000与serial@101f2000这样的同名结点.设备的unit-address地址也经常在其对应结点的reg属性中 给出.ePAPR标准给出了结点命名的规范.

可寻址的设备使用如下信息来在Device Tree中编码地址信息:

    reg

    #address-cells

    #size-cells

其中reg的组织形式为reg = <address1 length1 [address2 length2] [address3 length3] ... >,其中的每一组address length表明了设备使用的一个地址范围.address为1个或多个32位的整型(即cell),而length则为cell的列表或者为空 (若#size-cells = 0).address 和 length 字段是可变长的,父结点的#address-cells和#size-cells分别决定了子结点的reg属性的address和length字段的长度.在本例中,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 = <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>;其中address字段长度为2个cell,第一个cell(0、1、2)是对应的片选,第2个cell(0,0,0)是相对该片选的基地址,第3个cell(0x1000、0x1000、0x4000000)为length.特别要留意的是i2c结点中定义的 #address-cells = <1>;和#size-cells = <0>;又作用到了I2C总线上连接的RTC,它的address字段为0x58,是设备的I2C地址.因为I2C设备只是被分配在一个地址上,不需要其他任何空间,所以只需要一个address的cell就可以描述完整,不需要size-cells.

root结点的子结点描述的是CPU的视图,因此root子结点的address区域就直接位于CPU的memory区域.但是,要描述经过总线桥后的非本地设备的话,就不得不需要描述一下从设备地址空间到CPU地址空间的映射关系,此时就需要用到ranges属性. external-bus的ranges属性定义了经过external-bus桥后的地址范围如何映射到CPU的 memory区域.

        ranges = <   0x10100000   0x10000     // Chipselect 1, Ethernet

                     0x10160000   0x10000     // Chipselect 2, i2c controller

                     0x30000000   0x1000000>; // Chipselect 3, NOR Flash

ranges 是地址转换表,其中的每个项目是一个子地址、父地址以及在子地址空间的大小的映射.映射表中的子地址、父地址分别采用子地址空间的#address- cells和父地址空间的#address-cells大小.对于本例而言,子地址空间的#address-cells为2,父地址空间 的#address-cells值为1,因此0 0  0x10100000   0x10000的前2个cell为external-bus后片选0上偏移0,第3个cell表示external-bus后片选0上偏移0的地址空间被 映射到CPU的0x10100000位置,第4个cell表示映射的大小为0x10000.ranges的后面2个项目的含义可以类推.

Device Tree中还可以描述中断连接信息,需要四个属性:

interrupt-controller – 这个属性为空,用来声明这个node接收中断信号;

#interrupt-cells – 与#address-cells 和 #size-cells相似,这是中断控制器节点的属性,用来标识这个控制器需要几个单位做中断描述符;

在整个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就有如下文字说明:

The 1st cell is the interrupt type;  for SPI interrupts,  for PPI

interrupts.

The 2nd cell contains the interrupt number for the interrupt type.

SPI interrupts are in the range [-].  PPI interrupts are in the

range [-].

The 3rd cell is the flags, encoded as follows:

      bits[:] trigger type and level flags.

               = low-to-high edge triggered

               = high-to-low edge triggered

               = active high level-sensitive

               = active low level-sensitive

      bits[:] PPI interrupt cpu mask.  Each bit corresponds to each of

      the  possible cpus attached to the GIC.  A bit set to '' 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>;

除了中断以外,在ARM Linux中clock、GPIO、pinmux都可以透过.dts中的结点和属性进行描述.

DTC (device tree compiler)

将.dts 编译为.dtb的工具.DTC的源代码位于内核的scripts/dtc目录,在Linux内核使能了Device Tree的情况下,编译内核的时候主机工具dtc会被编译出来,对应scripts/dtc/Makefile中的“hostprogs-y := dtc”这一hostprogs编译target.

在Linux内核的arch/arm/boot/dts/Makefile中,描述了当某种SoC被选中后,哪些.dtb文件会被编译出来,如与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

在Linux下,我们可以单独编译Device Tree文件.当我们在Linux内核下运行make dtbs时,若我们之前选择了ARCH_VEXPRESS,上述.dtb都会由对应的.dts编译出来.因为arch/arm/Makefile中含有一 个dtbs编译target项目.

Device Tree Blob (.dtb)

.dtb是.dts被DTC编译后的二进 制格式的Device Tree描述,可由Linux内核解析.通常在我们为电路板制作NAND、SD启动image时,会为.dtb文件单独留下一个很小的区域以存放之,之后 bootloader在引导kernel的过程中,会先读取该.dtb到内存.

Binding

对于Device Tree中的结点和属性具体是如何来描述设备的硬件细节的,一般需要文档来进行讲解,文档的后缀名一般为.txt.这些文档位于内核的Documentation/devicetree/bindings目录,其下又分为很多子目录.

Bootloader

Uboot mainline 从 v1.1.3开始支持Device Tree,其对ARM的支持则是和ARM内核支持Device Tree同期完成.

为了使能Device Tree,需要编译Uboot的时候在config文件中加入

#define CONFIG_OF_LIBFDT

在Uboot中,可以从NAND、SD或者TFTP等任意介质将.dtb读入内存,假设.dtb放入的内存地址为0x71000000,之后可在Uboot运行命令fdt addr命令设置.dtb的地址,如:

U-Boot> fdt addr 0x71000000

fdt的其他命令就变地可以使用,如fdt resize、fdt print等.

对 于ARM来讲,可以透过bootz kernel_addr initrd_address dtb_address的命令来启动内核,即dtb_address作为bootz或者bootm的最后一次参数,第一个参数为内核映像的地址,第二个参 数为initrd的地址,若不存在initrd,可以用 -代替.

3. Device Tree引发的BSP和驱动变更

有了Device Tree后,大量的板级信息都不再需要,譬如过去经常在arch/arm/plat-xxx和arch/arm/mach-xxx实施的如下事情:

注册platform_device,绑定resource,即内存、IRQ等板级信息

透过Device Tree后,形如

static struct resource xxx_resources[] = {

        [] = {

                .start  = …,

                .end    = …,

                .flags  = IORESOURCE_MEM,

        },

        [] = {

                .start  = …,

                .end    = …,

                .flags  = IORESOURCE_IRQ,

         },

 };

static struct platform_device xxx_device = {

         .name           = "xxx",

         .id             = -,

         .dev            = {

                                 .platform_data          = &xxx_data,

         },

         .resource       = xxx_resources,

         .num_resources  = ARRAY_SIZE(xxx_resources),

};

之类的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:

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

注册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),

        },
};

之类的i2c_board_info代码,目前不再需要出现,现在只需要把tlv320aic23、fm3130、24c64这些设备结点填充作为相应的I2C controller结点的子结点即可,类似于前面的

      i2c@, {

            compatible = "acme,a1234-i2c-bus";

            …

            rtc@ {

                compatible = "maxim,ds1338";

                reg = <>;

                interrupts = <   >;

            };

        };

Device Tree中的I2C client会透过I2C host驱动的probe()函数中调用of_i2c_register_devices(&i2c_dev->adapter);被自动展开.

注册spi_board_info,指定IRQ等板级信息

形如

static struct spi_board_info afeb9260_spi_devices[] = {

        {       /* DataFlash chip */

                .modalias       = "mtd_dataflash",

                .chip_select    = ,

                .max_speed_hz   =  *  * ,

                .bus_num        = ,

        },

};

之类的spi_board_info代码,目前不再需要出现,与I2C类似,现在只需要把mtd_dataflash之类的结点, 作为SPI控制器的子结点即可,SPI host驱动的probe函数透过spi_register_master()注册master的时候,会自动展开依附于它的slave.

多个针对不同电路板的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

这些不同的machine会有不同的 MACHINE ID,Uboot在启动Linux内核时会将MACHINE ID存放在r1寄存器,Linux启动时会匹配Bootloader传递的MACHINE ID和MACHINE_START声明的MACHINE ID,然后执行相应machine的一系列初始化函数.

引入Device Tree之后,MACHINE_START变更为DT_MACHINE_START,其中含有一个.dt_compat成员,用于表明相关的 machine与.dts中root结点的compatible属性兼容关系.如果Bootloader传递给内核的Device Tree中root结点的compatible属性出现在某machine的.dt_compat表中,相关的machine就与对应的Device Tree匹配,从而引发这一machine的一系列初始化函数被执行.

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属性字符串.之后,如果的电路板的初始化序列不一 样,可以透过int of_machine_is_compatible(const char *compat) 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 <kgene.kim@samsung.com> */

        .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);

}

使用Device Tree后,驱动需要与.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外设驱动和Device Tree中设备结点的compatible 属性还有一种弱式匹配方法,就是别名匹配.compatible 属性的组织形式为<manufacturer>,<model>,别名其实就是去掉compatible 属性中逗号前的manufacturer前缀.关于这一点,可查看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 ;

        /* Then try ACPI */

         if (acpi_driver_match_device(dev, drv))

                 return ;

         if (sdrv->id_table)

                 return !!spi_match_id(sdrv->id_table, spi);

         return strcmp(spi->modalias, drv->name) == ;

 }

static const struct spi_device_id *spi_match_id(const struct spi_device_id *id,

                                                const struct spi_device *sdev)

{

        while (id->name[]) {

                if (!strcmp(sdev->modalias, id->name))

                        return id;

                id++;

        }

        return NULL;

}

4. 常用OF API

在Linux的BSP和驱动代码中,还经常会使用到Linux中一组Device Tree的API,这些API通常被冠以of_前缀,它们的实现代码位于内核的drivers/of目录.这些常用的API包括:

int of_device_is_compatible(const struct device_node *device,const char *compat);

判 断设备结点的compatible 属性是否包含compat指定的字符串.当一个驱动支持2个或多个设备的时候,这些不同.dts文件中设备的compatible 属性都会进入驱动 OF匹配表.因此驱动可以透过Bootloader传递给内核的Device Tree中的真正结点的compatible 属性以确定究竟是哪一种设备,从而根据不同的设备类型进行不同的处理.如drivers/pinctrl/pinctrl-sirf.c即兼容 于"sirf,prima2-pinctrl",又兼容于"sirf,prima2-pinctrl",在驱动中就有相应分支处理:

if (of_device_is_compatible(np, "sirf,marco-pinctrl"))

     is_marco = ;

struct device_node *of_find_compatible_node(struct device_node *from,

         const char *type, const char *compatible);

根据compatible属性,获得设备结点.遍历Device Tree中所有的设备结点,看看哪个结点的类型、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位整型数组的属性.对于32位处理器来讲,最常用的是 of_property_read_u32_array().如在arch/arm/mm/cache-l2x0.c中,透过如下语句读取L2 cache的"arm,data-latency"属性:

of_property_read_u32_array(np, "arm,data-latency",

                           data, ARRAY_SIZE(data));

在arch/arm/boot/dts/vexpress-v2p-ca9.dts中,含有"arm,data-latency"属性的L2 cache结点如下:

L2: cache-controller@1e00a000 {

        compatible = "arm,pl310-cache";

        reg = <0x1e00a000 0x1000>;

        interrupts = <  >;

        cache-level = <>;

        arm,data-latency = <  >;

        arm,tag-latency = <  >;

}

有些情况下,整形属性的长度可能为1,于是内核为了方便调用者,又在上述API的基础上封装出了更加简单 的读单一整形属性的API,它们为int of_property_read_u8()、of_property_read_u16()等,实现于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, );

}

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, );

}

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, );

}

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"字符串数组属性.

const char *of_clk_get_parent_name(struct device_node *np, int index)

{

        struct of_phandle_args clkspec;

        const char *clk_name;

        int rc;

        if (index < )

                return NULL;

        rc = of_parse_phandle_with_args(np, "clocks", "#clock-cells", index,

                                        &clkspec);

        if (rc)

                return NULL;

        if (of_property_read_string_index(clkspec.np, "clock-output-names",

                                  clkspec.args_count ? clkspec.args[] : ,

                                          &clk_name) < )

                clk_name = clkspec.np->name;

        of_node_put(clkspec.np);

        return clk_name;

}

EXPORT_SYMBOL_GPL(of_clk_get_parent_name);

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.采用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头文件.

5. 总结

ARM 社区一贯充斥的大量垃圾代码导致Linus盛怒,因此社区在2011年到2012年进行了大量的工作.ARM Linux开始围绕Device Tree展开,Device Tree有自己的独立的语法,它的源文件为.dts,编译后得到.dtb,Bootloader在引导Linux内核的时候会将.dtb地址告知内核.之 后内核会展开Device Tree并创建和注册相关的设备,因此arch/arm/mach-xxx和arch/arm/plat-xxx中大量的用于注册platform、 I2C、SPI板级信息的代码被删除,而驱动也以新的方式和.dts中定义的设备结点进行匹配.

本文转自:http://21cnbao.blog.51cto.com/109393/1105647/

ARM Linux 3.x的设备树(Device Tree)的更多相关文章

  1. 【转载】Linux设备树&lpar;Device Tree&rpar;机制

    转:Linux设备树(Device Tree)机制   目录 1. 设备树(Device Tree)基本概念及作用2. 设备树的组成和使用 2.1. DTS和DTSI 2.2. DTC 2.3. DT ...

  2. ARM Linux 3&period;x的设备树(Device Tree)

    http://blog.csdn.net/21cnbao/article/details/8457546 宋宝华 Barry Song <21cnbao@gmail.com> 1.     ...

  3. 【转】 ARM Linux 3&period;x的设备树(Device Tree)

    1.    ARM Device Tree起源 http://blog.csdn.net/21cnbao/article/details/8457546 Linus Torvalds在2011年3月1 ...

  4. 【转】ARM Linux 3&period;x的设备树(Device Tree)

    原文网址:http://blog.csdn.net/21cnbao/article/details/8457546 1.    ARM Device Tree起源 Linus Torvalds在201 ...

  5. ARM Linux 3&period;x的设备树(Device Tree)【转】

    转自:http://blog.csdn.net/21cnbao/article/details/8457546 宋宝华 Barry Song <21cnbao@gmail.com> 1.  ...

  6. 转:Linux设备树&lpar;Device Tree&rpar;机制

    目录 1. 设备树(Device  Tree)基本概念及作用 2. 设备树的组成和使用 2.1. DTS和DTSI 2.2. DTC 2.3. DTB 2.4. Bootloader 3. 设备树中d ...

  7. ARM Linux 3&period;x的设备树(Device Tree)&lpar;转&rpar;

    http://blog.csdn.net/21cnbao/article/details/8457546

  8. linux驱动之获取设备树信息

    上一篇文章学习了字符设备的注册,操作过的小伙伴都知道上一篇文章中测试驱动时是通过手动创建设备节点的,现在开始学习怎么自动挂载设备节点和设备树信息的获取,这篇文章中的源码将会是我以后编写字符驱动的模板. ...

  9. Linux kernel 有关 spi 设备树参数解析

    一.最近做了一个 spi 设备驱动从板级设备驱动升级到设备树设备驱动,这其中要了解 spi 设备树代码的解析. 二. 设备树配置如下: 503 &spi0 { 504 status = &qu ...

随机推荐

  1. Unity3D安卓出包报错

    今天又遇到了在安卓出包时,直接报错了两个错误,报错信息分别如下: Installation failed with the following output: pkg: /data/local/tmp ...

  2. 页面modal服务

    /** * * * 初始化: * var oneModal = modalSvc.createModal(templateUrl, controller, size); * size可以是:lg或sm ...

  3. p类型最大可定义范围

    t7(16) TYPE  p DECIMALS 14,

  4. 使用JS模拟出Map对象

    近期要做的一个项目,支持方提供的一个Map方法,用着相当能够,功能稍有欠缺,因此我为之做了扩展,下面是代码: function Map() { this.elements = new Array(); ...

  5. JSON 转换异常 multipartRequestHandler servletWrapper

    下面出现红色的字还有警告,解决方法:本人项目是struts1 from类继承了“extends ActionForm” .把它去掉就行了.如果你是其它的框架一定是继承引起的作个参考吧. ....... ...

  6. 内存压测工具Memtester

    在做压力测试时,发现一个内存压测工具Memtester,可以随意设置内存占用大小,非常方便 下载地址:http://pyropus.ca/software/memtester/old-versions ...

  7. &lbrack;Swift&rsqb;LeetCode415&period; 字符串相加 &vert; Add Strings

    Given two non-negative integers num1 and num2 represented as string, return the sum of num1 and num2 ...

  8. luigi 学习

    1.mac 上安装luigi pip install luigi pip install boto3 (luigi依赖 boto3) 2.基本概念 class Streams(luigi.Task): ...

  9. Maven CXF wsdl2Java String生成JAXBElement&lt&semi;Xxx&gt&semi; 解决方法

    添加要bindingFile的jaxb配置文件,如下: <jaxb:bindings version="2.1" xmlns:jaxb="http://java.s ...

  10. Windows7系统中怎么Ping端口?利用telnet命令Ping 端口的方法

    telnet www.baidu.com 80 端口打开的情况下,链接成功,则进入Telnet页面(全黑的),证明端口可用.