device tree使用研究

1 devicetree 概述

在ARM Linux中,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的hardcode,很多代码只是在描述板级细节。如板上的platform设备、resource、i2c_board_info、spi_board_info以及各种硬件的platform_data,为了改变这种局面,于是PowerPC等其他体系架构下已经使用的Flattened Device Tree(FDT)进入ARM社区的视野。

Device Tree是一种描述硬件的数据结构,它起源于OpenFirmware (OF)。采用Device Tree后,

许多硬件的细节可以直接透过它传递给Linux,而不再需要在kernel中进行大量的冗余编码。

Devicetree会去解析并扩展dts文件,并生成platform_device,resouce,i2c_board_info等信息,并基于Linux驱动模型,把设备注册到系统中.

2 语义解析

 2.1 Device Treesource file语法介绍

Devictree的源文件后缀为*.dts,所有的*.dts经过DTC编译后生成DTB文件,内核负责解析DTB文件.

Device Tree由一系列被命名的结点(node)和属性(property)组成,而结点本身可包含子结点。所谓属性,其实就是成对出现的name和value,以M85项目的dts文件来分析:

/ {

         model= "MT6795";

         compatible= "mediatek,MT6795";

         interrupt-parent= <&gic>;

         #address-cells= <2>;

         #size-cells= <2>;

         /*chosen */

         chosen{

                   bootargs= "console=tty0 console=ttyMT0,921600n1 root=/dev/raminitrd=0x44000000,0x300000 loglevel=8";

         };

             /* Do not put any bus before mtk-msdc,because it should be mtk-msdc.0 for partition device node usage */

             mtk-msdc {

                            compatible= "simple-bus";

                            #address-cells= <1>;

                            #size-cells= <1>;

                   MSDC0@0x11230000{

                                     compatible= "mediatek,MSDC0";

                                     reg= <0x11230000 0x10000  /*MSDC0_BASE   */

                                            0x10001E84 0x2>;    /* FPGA PWR_GPIO, PWR_GPIO_EO */

                                     interrupts = <0 79 0x8>;

                            };         

 

             };

};

 

“/”表示根节点,chose,mtk-msdc为根节点的子节点

其中mtk-msdc节点有子节点MSDC0@0x11230000

model起表示作用

compatible : 设备名,用于和驱动进行匹配

interrupt-parent :标示中断控制器节点

 

#address-cell :

#size-cell

Reg

reg的组织形式为reg =<address1 length1 [address2 length2] [address3 length3] ... >,其中的每一组addresslength表明了设备使用的一个地址范围。

address为1个或多个32位的整型(即cell),而length则为cell的列表或者为空(若#size-cells= 0)。address 和 length 字段是可变长的,

父结点的#address-cells和#size-cells分别决定了子结点的reg属性的address和length字段的长度。

如一块内存的starting address为 0x100000000, 大小为 0x20000

不同的#address-cell和#size-cell表述如下

#address-cell =1  #size-cell =1

reg = <0x10000000 0x20000>

#address-cell =2  #size-cell =2

reg = <0x00000000 0x100000000 0x000000000x20000>

#address-cell =2  #size-cell =1

reg = <0x00000000 0x1000000000x20000>

 

interrupts和interrupts-cell

#interrupt-cells – 与#address-cells 和 #size-cells相似,它表明连接此中断控制器的设备的interrupts属性的cell大小

interrupts – 用到了中断的设备结点透过它指定中断号、触发方法等,具体这个属性含有多少个cell,由它依附的中断控制器结点的#interrupt-cells属性决定。而具体每个cell又是什么含义,一般由驱动的实现决定,而且也会在Device Tree的binding文档中说明。譬如,对于ARM GIC中断控制器而言,#interrupt-cells为3,它3个cell的具体含义Documentation/devicetree/bindings/arm/gic.txt就有如下文字说明:

[plain] view plaincopy

  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 highlevel-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>;

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

 

2.2.2 DCT编译器

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

在Linux内核的arch/arm/boot/dts/Makefile中,描述了当某种SoC被选中后,哪些.dtb文件会被编译出来,如与VEXPRESS对应的.dtb包括:

[plain] view plaincopy

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项目。

2.2.3 DTB 文件格式

经过Device Tree Compiler编译,Device Tree source file变成了Device Tree Blob(又称作flatteneddevice tree)的格式。Device Tree Blob的数据组织如下图所示:

DTB header。

对于DTB header,其各个成员解释如下:

header field name   description

magic       用来识别DTB的。通过这个magic,kernel可以确定bootloader传递的参数block是一个DTB还是tag list。

totalsize  DTB的total size

off_dt_struct   device tree structure block的offset

off_dt_strings device tree strings block的offset

off_mem_rsvmap    offset to memory reserve map。有些系统,我们也许会保留一些memory有特殊用途(例如DTB或者initrdimage),或者在有些DSP+ARM的SOC platform上,有写memory被保留用于ARM和DSP进行信息交互。这些保留内存不会进入内存管理系统。

version     该DTB的版本。

last_comp_version  兼容版本信息

boot_cpuid_phys     我们在哪一个CPU(用ID标识)上booting

dt_strings_size        device tree strings block的size。和off_dt_strings一起确定了stringsblock在内存中的位置

dt_struct_size device tree structure block的size。和和off_dt_struct一起确定了devicetree structure block在内存中的位置

3.源码分析

主要分析从DTB解析出node和注册node到系统

3.1 device node创建

设备树中的每个节点都由struect device_node表示

struct device_node {

   const char *name;---device node name

   const char *type  ----对应device_type的属性

   phandle phandle; ---对应该节点的phandle属性

   const char *full_name; -----从“/”开始的,表示该node的full path

   struct    property *properties;---------该节点的属性列表

   struct    property *deadprops; --如果需要删除某些属性,kernel并非真的删除,而是挂入到deadprops的列表

   struct    device_node *parent;---parent、child以及sibling将所有的devicenode连接起来

   struct    device_node *child;---------指向第一个child节点

   struct    device_node *sibling; -------指向兄弟节点

   struct    device_node *next;  -----通过该指针可以获取相同类型的下一个node

   struct    device_node *allnext;---通过该指针可以获取nodeglobal list下一个node

   struct    proc_dir_entry *pde;----开放到userspace的proc接口信息

   struct    kref kref;     ----该node的referencecount

   unsigned long _flags;

   void    *data;

};

__unflatten_device_tree函数中实现了转换:

void __init unflatten_device_tree(void)

{

   unsigned long start, mem, size;

   struct device_node **allnextp = &allnodes;//设备树的头节点

   /* First pass, scan for size */

   start = ((unsigned long)initial_boot_params) +

       be32_to_cpu(initial_boot_params->off_dt_struct);

   size = unflatten_dt_node(0, &start, NULL, NULL, 0);//第一次调用mem传0,allnextpp传NULL,实际上是为了计算整个设备树所要的空间

   size = (size | 3) + 1;

   /*分配所有Node需要的内存 */

   /* Allocate memory for the expanded device tree */

   mem = early_init_dt_alloc_memory_arch(size + 4,

           __alignof__(struct device_node));

    mem= (unsigned long) __va(mem);

   ((__be32 *)mem)[size / 4] = cpu_to_be32(0xdeadbeef);//越界检测

    /* Second pass, do actual unflattening */

   start = ((unsigned long)initial_boot_params) +

       be32_to_cpu(initial_boot_params->off_dt_struct);

   /*内存分配完毕,进行整个设备树解析 */

   unflatten_dt_node(mem, &start, NULL, &allnextp, 0);//生成整个设备树

}

 

//drivers/of/fdt.c

unsigned long __initunflatten_dt_node(unsigned long mem,

                    unsigned long *p,

                    struct device_node *dad,

                    struct device_node***allnextpp,

                    unsigned long fpsize)

{

   struct device_node *np;

   struct property *pp, **prev_pp = NULL;

   char *pathp;

   u32 tag;

   unsigned int l, allocl;

   int has_name = 0;

   int new_format = 0;

   tag = be32_to_cpup((__be32 *)(*p));//每个有孩子的设备节点,其tag一定是OF_DT_BEGIN_NODE

   if (tag != OF_DT_BEGIN_NODE) {//

       pr_err("Weird tag at start of node: %x\n", tag);

       return mem;

    }

   *p += 4;//地址+4,这样指向节点的名称

   pathp = (char *)*p;

    l= allocl = strlen(pathp) + 1;//该节点名称的长度

   *p = _ALIGN(*p + l, 4);//*p指向带分析的属性

   /* version 0x10 has a more compact unit name here instead of the full

    * path. we accumulate the full path size using "fpsize", we'llrebuild

    * it later. We detect this because the first character of the name is

    * not '/'.

    */

   /*计算fullpath的长度 */

   if ((*pathp) != '/') {       new_format = 1;

       if (fpsize == 0) {/根跟节点

           /* root node: special case. fpsize accounts for path

            * plus terminating zero. root node only has '/', so

            * fpsize should be 2, but we want to avoid the first

            * level nodes to have two '/' so we use fpsize 1 here

            */

           fpsize = 1;

           allocl = 2;//代分配的长度

       } else {

           /* account for '/' and path size minus terminal 0

            * already in 'l'

            */

           fpsize += l;//代分配的长度=本节点名称长度+父亲节点绝对路径的长度

           allocl = fpsize;

       }

    }

 

   /*分配一个节点 */

   np = unflatten_dt_alloc(&mem, sizeof(struct device_node) + allocl,

                __alignof__(structdevice_node));//分配一个设备节点

   if (allnextpp) {//allnextpp指向前一个设备链表的指针

       memset(np, 0, sizeof(*np));

       np->full_name = ((char *)np) + sizeof(struct device_node);

       if (new_format) {

           char *fn = np->full_name;

           /* rebuild full path for new format */

           if (dad && dad->parent) {

                strcpy(fn,dad->full_name);//把父亲节点绝对路径先拷贝

                fn += strlen(fn);

           }

           *(fn++) = '/';

           memcpy(fn, pathp, l);//拷贝本节点的名称

       } else

           memcpy(np->full_name, pathp, l);

       prev_pp = &np->properties;// prev_pp指向节点的属性

       **allnextpp = np;//当前节点插入链表

       *allnextpp = &np->allnext;

       if (dad != NULL) {//父亲节点不为空

           np->parent = dad;//指向父亲节点

           /* we temporarily use the next field as `last_child'*/

           if (dad->next == NULL)//第一个孩子

                dad->child = np;//child指向第一个孩子

           else

                dad->next->sibling =np;//把np插入next,这样孩子节点形成链表

           dad->next = np;/*指向最新挂接的child node */

       }

       kref_init(&np->kref);

    }

         /*分析设备的属性*/

   while (1) {   

    u32 sz, noff;

       char *pname;

       tag = be32_to_cpup((__be32 *)(*p));

       if (tag == OF_DT_NOP) {//空属性,则跳过

           *p += 4;

           continue;

       }

       if (tag != OF_DT_PROP)//tag不是属性则退出,对于有孩子节点退出时为OF_DT_BEGIN_NODE,对于叶子节点退出时为OF_DT_END_NODE

           break;

       *p += 4;//地址加4

       sz = be32_to_cpup((__be32 *)(*p));//属性的大小,是以为占多少整形指针计算的。例如网卡interrupts = <35 2 36 2 40 2>;,其值为24

       noff = be32_to_cpup((__be32 *)((*p) + 4));//属性名称的便宜

       *p += 8;/*指向value */

       if (be32_to_cpu(initial_boot_params->version) < 0x10)

           *p = _ALIGN(*p, sz >= 8 ? 8 : 4);

       pname = find_flat_dt_string(noff);//属性名称

       if (pname == NULL) {

           pr_info("Can't find property name in list !\n");

           break;

       }

       if (strcmp(pname, "name") == 0)//如果有名称为name的属性

           has_name = 1;

       l = strlen(pname) + 1;

       pp = unflatten_dt_alloc(&mem, sizeof(struct property),

                    __alignof__(structproperty));//分配属性结构

       if (allnextpp) {

           /* We accept flattened tree phandles either in

            * ePAPR-style "phandle" properties, or the

            * legacy "linux,phandle" properties.  If both

            * appear and have different values, things

            * will get weird.  Don't do that.*/

           if ((strcmp(pname, "phandle") == 0) ||

                (strcmp(pname,"linux,phandle") == 0)) {

                if (np->phandle == 0)

                    np->phandle = *((u32*)*p);

           }

           /* And we process the "ibm,phandle" property

            * used in pSeries dynamic device tree

            * stuff */

           if (strcmp(pname, "ibm,phandle") == 0)

                np->phandle = *((u32 *)*p);

           pp->name = pname;//属性名

           pp->length = sz;//长度

           pp->value = (void *)*p;//属性值

            *prev_pp = pp;//属性插入属性链表

           prev_pp = &pp->next;

       }

       *p = _ALIGN((*p) + sz, 4);//指向下一个属性

    }

   /* with version 0x10 we may not have the name property, recreate

    * it here from the unit name if absent

    */

   if (!has_name) {//如果没有name,则为该节点生成一个name属性

       char *p1 = pathp, *ps = pathp, *pa = NULL;

       int sz;

       while (*p1) {

           if ((*p1) == '@')

                pa = p1;

           if ((*p1) == '/')

                ps = p1 + 1;

           p1++;

       }

       if (pa < ps)

           pa = p1;

       sz = (pa - ps) + 1;

       pp = unflatten_dt_alloc(&mem, sizeof(struct property) + sz,

                    __alignof__(structproperty));

       if (allnextpp) {

           pp->name = "name";

           pp->length = sz;

           pp->value = pp + 1;

           *prev_pp = pp;

           prev_pp = &pp->next;

           memcpy(pp->value, ps, sz - 1);//名字的值只去其中的@钱的字符,例如pcie@ffe0a000-> pcie

           ((char *)pp->value)[sz - 1] = 0;

           pr_debug("fixed up name for %s -> %s\n", pathp,

                (char *)pp->value);

       }

    }

   if (allnextpp) {//如果有属性链表

       *prev_pp = NULL;

       np->name = of_get_property(np, "name", NULL);//设置节点的名称

       np->type = of_get_property(np, "device_type", NULL);//设置设备类型

       if (!np->name)

           np->name = "<NULL>";

       if (!np->type)

           np->type = "<NULL>";

    }

   while (tag == OF_DT_BEGIN_NODE || tag == OF_DT_NOP) {//对于tag为这2个取值

       if (tag == OF_DT_NOP)//空属性则指向下个属性

           *p += 4;

       else

           mem = unflatten_dt_node(mem, p, np, allnextpp,fpsize);//OF_DT_BEGIN_NODE则表明其还有子节点,所以递归分析其子节点

       tag = be32_to_cpup((__be32 *)(*p));

    }

   if (tag != OF_DT_END_NODE) {//对于叶子节点或者分析完成

       pr_err("Weird tag at end of node: %x\n", tag);

       return mem;

    }

   *p += 4;

   return mem;

}

3.2 device node注册

解析完DTB以后,会调用of_platform_populate函数创建platform device,调用顺序如下:

of_platform_populate

of_platform_bus_create

    of_platform_device_create_pdata

      of_device_alloc

     of_device_add

    staticint of_platform_bus_create(struct device_node *bus,

                                       const struct of_device_id *matches,

                                       const struct of_dev_auxdata *lookup,

                                       struct device *parent, bool strict)

{

         conststruct of_dev_auxdata *auxdata;

         structdevice_node *child;

         structplatform_device *dev;

         constchar *bus_id = NULL;

         void*platform_data = NULL;

         intrc = 0;

 

         /*创建platformdevice结构 */

         dev= of_platform_device_create_pdata(bus, bus_id, platform_data, parent);

         if(!dev || !of_match_node(matches, bus))/*如果不匹配matchs,则不需要为子节点创建platform_device*/

         return0;

         /*遍历所有子节点,递归条用of_platform_bus_create*/

         for_each_child_of_node(bus,child) {

                   pr_debug("   create child: %s\n",child->full_name);

                   rc= of_platform_bus_create(child, matches, lookup, &dev->dev, strict);

         }

         returnrc;

}  

 

struct platform_device*of_platform_device_create_pdata(

                                               structdevice_node *np,

                                               constchar *bus_id,

                                               void*platform_data,

                                               structdevice *parent)

{

         structplatform_device *dev;

         /*分配platform_device结构 */

         dev= of_device_alloc(np, bus_id, parent);

         if(!dev)

                   returnNULL;

         dev->dev.coherent_dma_mask= DMA_BIT_MASK(32);

         dev->dev.bus= &platform_bus_type;

         dev->dev.platform_data= platform_data;

         /*把pltatform添加到系统中 */

         if(of_device_add(dev) != 0) {

                   platform_device_put(dev);

                   returnNULL;

         }

         returndev;

}

4 常用API

1.根据compatible找到对应的devicenode结构

struct device_node*of_find_compatible_node(struct device_node *from,

         constchar *type, const char *compatible)

 

更加full path 找到对应的device node

struct device_node*of_find_node_by_path(const char *path)

 

2. 在device node 中获取属性值

struct property *of_find_property(conststruct device_node *np,

                                       const char *name,

                                       int *lenp)

3. 检查device node的是否与compa匹配

int of_device_is_compatible(const structdevice_node *device,

                   constchar *compat)

4. 读取属性值

static inline intof_property_read_u32(const struct device_node *np,

                                            const char *propname,

                                            u32 *out_value)

5. 通过phandle引用node

struct device_node *of_parse_phandle(conststruct device_node *np,

                                          const char *phandle_name, int index)

6.映射中断

unsigned int irq_of_

5. 使用案例

以触摸屏为来说明如何使用.

另外i2c设备必须是i2c控制器的子设备才能顺利添加到系统中.

Dts文件如下:

/{

  bus{

         I2C2@0x11009000{

          status="okay";

          #size-cells = <0>;

     synaptics_touch@20{

          compatible = "synaptics,touch" ;

        reg= <0x20>;

        touch_irq=<2>;

       };

 

  };

};

驱动中添加:

static struct i2c_driversynaptics_i2c_driver = {

       .driver = {

                .name =  TPD_DEVICE,

                .owner = THIS_MODULE,

               #ifdef CONFIG_OF

                .of_match_table = &of_match,

               #endif

       },

       .probe = synaptics_rmi4_probe,

       .remove = synaptics_rmi4_remove,

       .id_table = tp_id,

};

static const struct of_device_id of_match[]= {

         {.compatible = "synaptics,touch",},/* 要和dts中的compatible匹配*/

         {/*sentinel */},

};

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值