转自:http://blog.csdn.net/qq_27677015/article/details/53415848
ARM Device Tree起源于OpenFirmware (OF),在过去的Linux中,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的垃圾代码,相当多数的代码只是在描述板级细节,而这些板级细节对于内核来讲,不过是垃圾,如板上的platform设备、resource、i2c_board_info、spi_board_info以及各种硬件的platform_data。为了改变这种局面,Linux社区的大牛们参考了PowerPC等体系架构中使用的Flattened Device Tree(FDT),也采用了Device Tree结构,许多硬件的细节可以直接透过它传递给Linux,而不再需要在kernel中进行大量的冗余编码。
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等资源,也被传递给了内核,内核会将这些资源绑定给展开的相应的设备。
通常由
二、相关结构体
1、U-Boot需要将设备树在内存中的存储地址传给内核。该树主要由三大部分组成:头(Header)、结构块(Structure block)、字符串块(Strings block)。
设备树在内存中的存储布局图:
1.1 头(header)
头主要描述设备树的一些基本信息,例如设备树大小,结构块偏移地址,字符串块偏移地址等。偏移地址是相对于设备树头的起始地址计算的。
struct
- __be32 off_dt_struct;
- __be32 off_mem_rsvmap;
- __be32 last_comp_version;
- __be32 dt_strings_size;
- };
1.2 结构块(struct block)
设备树结构块是一个线性化的结构体,是设备树的主体,以节点node的形式保存了目标单板上的设备信息。
在结构块中以宏OF_DT_BEGIN_NODE标志一个节点的开始,以宏OF_DT_END_NODE标识一个节点的结束,整个结构块以宏OF_DT_END结束。一个节点主要由以下几部分组成。
(1)节点开始标志:一般为OF_DT_BEGIN_NODE。
(2)节点路径或者节点的单元名(ersion<3以节点路径表示,version>=0x10以节点单元名表示)
(3)填充字段(对齐到四字节)
(4)节点属性。每个属性以宏OF_DT_PROP开始,后面依次为属性值的字节长度(4字节)、属性名称在字符串块中的偏移量(4字节)、属性值和填充(对齐到四字节)。
(5)如果存在子节点,则定义子节点。
(6)节点结束标志OF_DT_END_NODE。
1.3 字符串块
通过节点的定义知道节点都有若干属性,而不同的节点的属性又有大量相同的属性名称,因此将这些属性名称提取出一张表,当节点需要应用某个属性名称时直接在属性名字段保存该属性名称在字符串块中的偏移量。
1.4 设备树源码 DTS 表示
设备树源码文件(.dts)以可读可编辑的文本形式描述系统硬件配置设备树,支持
C/C++方式的注释,该结构有一个唯一的根节点“/”,每个节点都有自己的名字并可以包含多个子节点。设备树的数据格式遵循了 Open
Firmware IEEE standard
1275。这个设备树中有很多节点,每个节点都指定了节点单元名称。每一个属性后面都给出相应的值。以双引号引出的内容为 ASCII
字符串,以尖括号给出的是 32 位的16进制值。这个树结构是启动 Linux 内核所需节点和属性简化后的集合,包括了根节点的基本模式信息、CPU
和物理内存布局,它还包括通过/chosen 节点传递给内核的命令行参数信息。
1.5 machine_desc结构
内核提供了一个重要的结构体struct machine_desc ,这个结构体在内核移植中起到相当重要的作用,内核通过machine_desc结构体来控制系统体系架构相关部分的初始化。machine_desc结构体通过MACHINE_START宏来初始化,在代码中, 通过在start_kernel->setup_arch中调用setup_machine_fdt来获取。
- *name;
- unsigned atag_offset;
- * *dt_compat;
- unsigned nr_irqs;
- #ifdef CONFIG_ZONE_DMA
- video_start;
- video_end;
- reserve_lp0 :1;
- unsigned reserve_lp1 :1;
- reserve_lp2 :1;
- reboot_mode reboot_mode;
- smp_operations *smp;
- (*smp_init)();
- (*fixup)( tag *, **, meminfo *);
- (*init_meminfo)();
- (*reserve)();
- (*map_io)();
- (*init_early)();
- (*init_irq)();
- (*init_time)();
- (*init_machine)();
- (*init_late)();
- (*handle_irq)( pt_regs *);
- (*restart)( reboot_mode, *);
- };
1.6 设备节点结构体
- *type;
- phandle phandle;
- *full_name;
- property *properties;
- property *deadprops;
- device_node *parent;
- device_node *child;
- device_node *sibling;
- device_node *next;
- device_node *allnext;
- proc_dir_entry *pde;
- kref kref;
- unsigned _flags;
- *data;
- *path_component_name;
- unsigned unique_id;
- of_irq_controller *irq_trans;
- };
1.7 属性结构体
- length;
- *value;
- property *next;
- unsigned _flags;
- unique_id;
- };
三、设备树初始化及解析
分析Linux内核的源码,可以看到其对扁平设备树的解析流程如下:
//kernel 初始化的代码(init/main.c) __init start_kernel()
- setup_arch(&command_line);
- void **cmdline_p)
- machine_desc *mdesc;
- mdesc = setup_machine_fdt(__atags_pointer);
- (!mdesc)
- mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
- (mdesc->reboot_mode != REBOOT_HARD)
- ) _text;
- init_mm.end_code = (unsigned ) _etext;
- ) _edata;
- init_mm.brk = (unsigned ) _end;
- (meminfo.bank[0]), meminfo_cmp, NULL);
- (mdesc->restart)
- arm_pm_restart = mdesc->restart;
- }</span>
(一) 函数获取内核前期初始化所需的bootargs,cmd_line等系统引导参数
1. setup_machine_fdt()函数获取内核前期初始化所需的bootargs,cmd_line等系统引导参数。
const machine_desc * __init setup_machine_fdt(unsigned dt_phys)
- {
- machine_desc *mdesc, *mdesc_best = NULL;
- #ifdef CONFIG_ARCH_MULTIPLATFORM #endif
- (!dt_phys || !early_init_dt_scan(phys_to_virt(dt_phys)))
- NULL;
- mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
- (!mdesc) {
- *prop;
- size;
- unsigned dt_root;
- , &size);
- (size > 0) {
- early_print( );
- __machine_arch_type = mdesc->nr;
- mdesc;
- struct bool *params)
- (!params)
- ;
- initial_boot_params = params;
- (be32_to_cpu(initial_boot_params->magic) != OF_DT_HEADER) {
- initial_boot_params = NULL;
- ;
- }
- of_scan_flat_dt(early_init_dt_scan_root, NULL);
- ;
- }
- int (*it)(unsigned node, *uname, depth, *data), *data)
- p = ((unsigned )initial_boot_params) + be32_to_cpu(initial_boot_params->off_dt_struct);
- rc = 0;
- depth = -1;
- {
- *pathp;
- (tag == OF_DT_END_NODE) {
- depth--;
- ;
- }
- (tag == OF_DT_NOP)
- ;
- (tag == OF_DT_END)
- ;
- (tag == OF_DT_PROP) {
- (be32_to_cpu(initial_boot_params->version) < 0x10)
- p = ALIGN(p, sz >= 8 ? 8 : 4);
- p += sz;
- p = ALIGN(p, 4);
- ;
- (tag != OF_DT_BEGIN_NODE) {
- -EINVAL;
- depth++;
- pathp = ( *)p;
- p = ALIGN(p + strlen(pathp) + 1, 4);
- (*pathp == )
- (rc != 0)
- ;
- } (1);
- rc;
- }
1.1 chosen节点
linux,initrd-start = <0x85500000>; //节点属性
linux,initrd-end = <0x855a3212>; //节点属性
};
int node, *uname, depth, *data)
- {
- l;
- *p;
- (depth != 1 || !data || (strcmp(uname, ) != 0 && strcmp(uname, ) != 0))
- 0;
- p = of_get_flat_dt_prop(node, , &l);
- (p != NULL && l > 0)
- strlcpy(data, p, min(()l, COMMAND_LINE_SIZE));
- *)data);
- 1;
- static __init early_init_dt_check_for_initrd(unsigned node)
- {
- len;
- prop = of_get_flat_dt_prop(node, , &len);
- (!prop)
- ;
- start = of_read_number(prop, len/4);
- , &len);
- (!prop)
- ;
- initrd_start = (unsigned )__va(start);
- )__va(end);
- initrd_below_start_ok = 1;
- )start, (unsigned )end);
- void node, *name,unsigned *size)
- {
- of_fdt_get_property(initial_boot_params, node, name, size);
- }
- void boot_param_header *blob,unsigned node, *name,unsigned *size)
- p = node;
- {
- u32 tag = be32_to_cpup((__be32 *)p);
- *nstr;
- (tag == OF_DT_NOP)
- ;
- (tag != OF_DT_PROP)
- NULL;
- (be32_to_cpu(blob->version) < 0x10)
- nstr = of_fdt_get_string(blob, noff);
- (nstr == NULL) {
- pr_warning( NULL;
- }
- (strcmp(name, nstr) == 0) {
- (size)
- ( *)p;
- }
- p += sz;
- (1);
- char boot_param_header *blob, u32 offset)
- {
- (( *)blob) + be32_to_cpu(blob->off_dt_strings) + offset;
- static u64 of_read_number( __be32 *cell, size)
- {
- (size--)
- r = (r << 32) | be32_to_cpu(*(cell++));
- r;
- }
1.2 根节点"/"
compatible = "sprd,spx15";
#address-cells = <1>;
#size-cells = <1>;
interrupt-parent = <&gic>;
chosen {
bootargs = "loglevel=8 console=ttyS1,115200n8 init=/init root=/dev/ram0 rw";
linux,initrd-start = <0x85500000>;
linux,initrd-end = <0x855a3212>;
};
}
int node, *uname, depth, *data)
- {
- (depth != 0)
- 0;
- , NULL);
- (prop)
- prop = of_get_flat_dt_prop(node, , NULL);
- (prop)
- dt_root_addr_cells = be32_to_cpup(prop);
- 1;
- }
memory节点用于描述目标板上物理内存范围,一般称作/memory节点,可以有一个或多个。当有多个节点时,需要后跟单元地址予以区分;只有一个单元地址时,可以不写单元地址,默认为0。此节点包含板上物理内存的属性,一般要指定device_type(固定为"memory")和reg属性。其中reg的属性值以<起始地址 空间大小>的形式给出,如下示例中目标板内存起始地址为0x80000000,大小为0x20000000字节。
memory {
device_type = "memory";
reg = <0x80000000 0x20000000>;
};
int node, *uname, depth, *data)
- {
- *type = of_get_flat_dt_prop(node, , NULL);
- l;
- (type == NULL) {
- (depth != 1 || strcmp(uname, ) != 0)
- 0;
- } (strcmp(type, ) != 0)
- 0;
- reg = of_get_flat_dt_prop(node, , &l);
- (reg == NULL)
- reg = of_get_flat_dt_prop(node, , &l);
- (reg == NULL)
- 0;
- (__be32));
- ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {
- base = dt_mem_next_cell(dt_root_addr_cells, ®);
- (size == 0)
- ;
- )base,(unsigned )size);
- early_init_dt_add_memory_arch(base, size);
- 0;
- }
2. 通过比较根节点属性compatible值指明和目标板为同一系列的兼容的开发板名称
const * __init of_flat_dt_match_machine( *default_match,
- * (*get_next_compat)( * **))
- *data = NULL;
- *best_data = default_match;
- * *compat;
- dt_root;
- unsigned best_score = ~1, score = 0;
- ((data = get_next_compat(&compat))) {
- score = of_flat_dt_match(dt_root, compat);
- (score > 0 && score < best_score) {
- (!best_data) {
- *prop;
- size;
- , &size);
- (prop) {
- (size > 0) {
- );
- NULL;
- }
- best_data;
- //查找设备树的根节点 __init of_get_flat_dt_root()
- {
- unsigned p = ((unsigned )initial_boot_params) +
- (be32_to_cpup((__be32 *)p) == OF_DT_NOP)
- ALIGN(p + strlen(( *)p) + 1, 4);
- }
- //arch/arm/kernel/devtree.c 是location counter。在__arch_info_begin 的位置上,放置所有文件中的 段的内容,然后紧接着是 __arch_info_end 的位置. 段中定义了设备的machine_desc结构。
- static * __init arch_get_next_mach( * **match)
- {
- machine_desc *mdesc = __arch_info_begin;
- machine_desc *m = mdesc;
- (m >= __arch_info_end)
- NULL;
- *match = m->dt_compat;
- m;
- }
- //与设备树根节点进行match int node, * *compat)
- {
- of_fdt_match(initial_boot_params, node, compat);
- int boot_param_header *blob, unsigned node, * *compat)
- {
- tmp, score = 0;
- (!compat)
- 0;
- (*compat) {
- (tmp && (score == 0 || (tmp < score)))
- compat++;
- score;
- }
- int boot_param_header *blob,unsigned node, *compat)
- *cp;
- cplen, l, score = 0;
- cp = of_fdt_get_property(blob, node, , &cplen);
- (cp == NULL)
- 0;
- (cplen > 0) {
- score++;
- (of_compat_cmp(cp, compat, strlen(compat)) == 0)
- score;
- 0;
- }
(二)、解析设备树
unflatten_device_tree()函数来解析dtb文件,构建一个由device_node结构连接而成的单项链表,并使用全局变量of_allnodes指针来保存这个链表的头指针。内核调用OF提供的API函数获取of_allnodes链表信息来初始化内核其他子系统、设备。
void)
- {
- __unflatten_device_tree(initial_boot_params, &of_allnodes,early_init_dt_alloc_memory_arch);
- static __unflatten_device_tree( boot_param_header *blob,
- device_node **mynodes,
- * (*dt_alloc)(u64 size, u64 align))
- size;
- *start, *mem;
- device_node **allnextp = mynodes;
- (!blob) {
- ;
- (be32_to_cpu(blob->magic) != OF_DT_HEADER) {
- pr_err( ;
- }
- *)blob) + be32_to_cpu(blob->off_dt_struct);
- )unflatten_dt_node(blob, 0, &start, NULL, NULL, 0);
- size = ALIGN(size, 4);
- device_node));
- memset(mem, 0, size);
- *)blob) + be32_to_cpu(blob->off_dt_struct);
- (be32_to_cpup(start) != OF_DT_END)
- (be32_to_cpup(mem + size) != 0xdeadbeef)
- static * unflatten_dt_node( boot_param_header *blob,
- *mem, **p,
- device_node *dad,
- device_node ***allnextpp,
- fpsize)
- {
- device_node *np;
- property *pp, **prev_pp = NULL;
- *pathp;
- u32 tag;
- l, allocl;
- has_name = 0;
- new_format = 0;
- tag = be32_to_cpup(*p);
- (tag != OF_DT_BEGIN_NODE) {
- mem;
- pathp = *p;
- *p = PTR_ALIGN(*p + l, 4);
- ((*pathp) != ) {
- new_format = 1;
- (fpsize == 0) {
- fpsize = 1;
- ;
- } {
- allocl = fpsize;
- ( device_node) + allocl,__alignof__( device_node));
- (allnextpp) {
- *fn;
- np->full_name = fn = (( *)np) + (*np);
- (new_format) {
- (dad && dad->parent) {
- strcpy(fn, dad->full_name);
- ;
- }
- prev_pp = &np->properties;
- (dad != NULL) {
- np->parent = dad;
- (dad->next == NULL)
- dad->child = np;
- dad->next->sibling = np;
- (1) {
- u32 sz, noff;
- *pname;
- tag = be32_to_cpup(*p);
- (tag == OF_DT_NOP) {
- ;
- (tag != OF_DT_PROP)
- ;
- *p += 4;
- sz = be32_to_cpup(*p);
- noff = be32_to_cpup(*p + 4);
- *p += 8;
- (be32_to_cpu(blob->version) < 0x10)
- pname = of_fdt_get_string(blob, noff);
- (pname == NULL) {
- pr_info( ;
- }
- (strcmp(pname, ) == 0)
- has_name = 1;
- l = strlen(pname) + 1;
- pp = unflatten_dt_alloc(&mem, ( property),__alignof__( property));
- (allnextpp) {
- ((strcmp(pname, ) == 0) || (strcmp(pname, ) == 0)) {
- (np->phandle == 0)
- (strcmp(pname, ) == 0)
- np->phandle = be32_to_cpup((__be32 *)*p);
- pp->length = sz;
- *prev_pp = pp;
- }
- (!has_name) {
- *p1 = pathp, *ps = pathp, *pa = NULL;
- sz;
- (*p1) {
- ((*p1) == )
- pa = p1;
- ((*p1) == )
- ps = p1 + 1;
- (pa < ps)
- pa = p1;
- ( property) + sz,__alignof__( property));
- (allnextpp) {
- pp->name = ;
- *)pp->value)[sz - 1] = 0;
- *)pp->value);
- }
- (allnextpp) {
- , NULL);
- , NULL);
- (!np->name)
- np->name = (!np->type)
- np->type =
- (tag == OF_DT_BEGIN_NODE || tag == OF_DT_NOP) {
- (tag == OF_DT_NOP)
- *p += 4;
- (tag != OF_DT_END_NODE) {
- mem;
- mem;
- //从mem分配内存空间,*mem记录分配了多大空间 static *unflatten_dt_alloc( **mem, unsigned size,unsigned align)
- {
- *res;
- res;
- }
- //一个特定的节点通常是以完整的路径来引用,比如/external-bus/[email protected],0,不过当一个用户真的想知道“哪个设备是eth0”时,这将会很繁琐。aliases节点可以用来为一个完整的设备路径分配一个短的别名。比如: //aliases { // serial0 = &uart0; // serial1 = &uart1; // serial2 = &uart2; // serial3 = &uart3; // ethernet0 = ð0; // serial0 = &serial0; //}; //当需要为设备指定一个标示符时,操作系统欢迎大家使用别名。 //设置内核输出终端,以及遍历“/aliases”节点下的所有的属性,挂入相应链表 void * (*dt_alloc)(u64 size, u64 align))
- {
- property *pp;
- of_chosen = of_find_node_by_path();
- (of_chosen == NULL)
- of_chosen = of_find_node_by_path();
- (of_chosen) {
- *name;
- , NULL);
- (name)
- of_stdout = of_find_node_by_path(name);
- of_aliases = of_find_node_by_path();
- (!of_aliases)
- ;
- *start = pp->name;
- *end = start + strlen(start);
- device_node *np;
- alias_prop *ap;
- id, len;
- (!strcmp(pp->name, ) ||
- !strcmp(pp->name, ) ||
- ))
- ;
- (!np)
- ;
- (isdigit(*(end-1)) && end > start)
- (kstrtoint(end, 10, &id) < 0)
- ;
- ap = dt_alloc((*ap) + len + 1, 4);
- (!ap)
- ;
- (*ap) + len + 1);
- ap->alias = start;
- of_alias_add(ap, np, id, start, len);
- }
OF提供的函数主要集中在drivers/of/目录下,有address.c,base.c,device.c,fdt.c,irq.c,platform.c等等
1. 用来查找在dtb中的根节点
unsigned long __init of_get_flat_dt_root(void)
2. 根据deice_node结构的full_name参数,在全局链表of_allnodes中,查找合适的device_node
struct device_node *of_find_node_by_path(const char *path)
例如:
struct device_node *cpus;
cpus=of_find_node_by_path("/cpus");
3. 若from=NULL,则在全局链表of_allnodes中根据name查找合适的device_node
struct device_node *of_find_node_by_name(struct device_node *from,const char *name)
例如:
struct device_node *np;
np = of_find_node_by_name(NULL,"firewire");
4. 根据设备类型查找相应的device_node
struct device_node *of_find_node_by_type(struct device_node *from,const char *type)
例如:
struct device_node *tsi_pci;
tsi_pci= of_find_node_by_type(NULL,"pci");
5. 根据compatible字符串查找device_node
struct device_node *of_find_compatible_node(struct device_node *from,const char *type, const char *compatible)
6. 根据节点属性的name查找device_node
struct device_node *of_find_node_with_property(struct device_node *from,const char *prop_name)
7. 根据phandle查找device_node
struct device_node *of_find_node_by_phandle(phandle handle)
8. 根据alias的name获得设备id号
int of_alias_get_id(struct device_node *np, const char *stem)
9. device node计数增加/减少
struct device_node *of_node_get(struct device_node *node)
void of_node_put(struct device_node *node)
10. 根据property结构的name参数,在指定的device node中查找合适的property
struct property *of_find_property(const struct device_node *np,const char *name,int *lenp)
11. 根据property结构的name参数,返回该属性的属性值
const void *of_get_property(const struct device_node *np, const char *name,int *lenp)
12. 根据compat参数与device node的compatible匹配,返回匹配度
int of_device_is_compatible(const struct device_node *device,const char *compat)
13. 获得父节点的device node
struct device_node *of_get_parent(const struct device_node *node)
14. 将matches数组中of_device_id结构的name和type与device node的compatible和type匹配,返回匹配度最高的of_device_id结构
const struct of_device_id *of_match_node(const struct of_device_id *matches,const struct device_node *node)
15. 根据属性名propname,读出属性值中的第index个u32数值给out_value
int of_property_read_u32_index(const struct device_node *np,const char *propname,u32 index, u32 *out_value)
16. 根据属性名propname,读出该属性的数组中sz个属性值给out_values
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)
17. 根据属性名propname,读出该属性的u64属性值
int of_property_read_u64(const struct device_node *np, const char *propname,u64 *out_value)
18. 根据属性名propname,读出该属性的字符串属性值
int of_property_read_string(struct device_node *np, const char *propname,const char **out_string)
19. 根据属性名propname,读出该字符串属性值数组中的第index个字符串
int of_property_read_string_index(struct device_node *np, const char *propname,int index, const char **output)
20. 读取属性名propname中,字符串属性值的个数
int of_property_count_strings(struct device_node *np, const char *propname)
21. 读取该设备的第index个irq号
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
22. 读取该设备的第index个irq号,并填充一个irq资源结构体
int of_irq_to_resource(struct device_node *dev, int index, struct resource *r)
23. 获取该设备的irq个数
int of_irq_count(struct device_node *dev)
24. 获取设备寄存器地址,并填充寄存器资源结构体
int of_address_to_resource(struct device_node *dev, int index,struct resource *r)
const __be32 *of_get_address(struct device_node *dev, int index, u64 *size,unsigned int *flags)
25. 获取经过映射的寄存器虚拟地址
void __iomem *of_iomap(struct device_node *np, int index)
24. 根据device_node查找返回该设备对应的platform_device结构
struct platform_device *of_find_device_by_node(struct device_node *np)
25. 根据device node,bus id以及父节点创建该设备的platform_device结构
struct platform_device *of_device_alloc(struct device_node *np,const char *bus_id,struct device *parent)
static struct platform_device *of_platform_device_create_pdata(struct device_node *np,const char *bus_id,
void *platform_data,struct device *parent)
26. 遍历of_allnodes中的节点挂接到of_platform_bus_type总线上,由于此时of_platform_bus_type总线上还没有驱动,所以此时不进行匹配
int of_platform_bus_probe(struct device_node *root,const struct of_device_id *matches,struct device *parent)
27. 遍历of_allnodes中的所有节点,生成并初始化platform_device结构
int of_platform_populate(struct device_node *root,const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,struct device *parent)
{
struct device_node *child;
int rc = 0;
//获得设备树的根节点
root = root ? of_node_get(root) : of_find_node_by_path("/