一、描述
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等资源,也被传递给了内核,内核会将这些资源绑定给展开的相应的设备。
通常由.dts文件以文本方式对系统设备树进行描述,经过Device Tree Compiler(dtc)将dts文件转换成二进制文件binary device tree blob(dtb),.dtb文件可由Linux内核解析,有了device tree就可以在不改动Linux内核的情况下,对不同的平台实现无差异的支持,只需更换相应的dts文件,即可满足。
二、相关结构体
1、U-Boot需要将设备树在内存中的存储地址传给内核。该树主要由三大部分组成:头(Header)、结构块(Structure block)、字符串块(Strings block)。
设备树在内存中的存储布局图:

1.1 头(header)
头主要描述设备树的一些基本信息,例如设备树大小,结构块偏移地址,字符串块偏移地址等。偏移地址是相对于设备树头的起始地址计算的。
- struct boot_param_header {
- __be32 magic;
- __be32 totalsize;
- __be32 off_dt_struct;
- __be32 off_dt_strings;
- __be32 off_mem_rsvmap;
- __be32 version;
- __be32 last_comp_version;
- __be32 boot_cpuid_phys;
- __be32 dt_strings_size;
- __be32 dt_struct_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来获取。
- struct machine_desc {
- unsigned int nr;
- const char *name;
- unsigned long atag_offset;
- const char *const *dt_compat;
- unsigned int nr_irqs;
-
- #ifdef CONFIG_ZONE_DMA
- phys_addr_t dma_zone_size;
- #endif
-
- unsigned int video_start;
- unsigned int video_end;
-
- unsigned char reserve_lp0 :1;
- unsigned char reserve_lp1 :1;
- unsigned char reserve_lp2 :1;
- enum reboot_mode reboot_mode;
- struct smp_operations *smp;
- bool (*smp_init)(void);
- void (*fixup)(struct tag *, char **,struct meminfo *);
- void (*init_meminfo)(void);
- void (*reserve)(void);
- void (*map_io)(void);
- void (*init_early)(void);
- void (*init_irq)(void);
- void (*init_time)(void);
- void (*init_machine)(void);
- void (*init_late)(void);
- #ifdef CONFIG_MULTI_IRQ_HANDLER
- void (*handle_irq)(struct pt_regs *);
- #endif
- void (*restart)(enum reboot_mode, const char *);
- };
1.6 设备节点结构体
- struct device_node {
- const char *name;
- const char *type;
- phandle phandle;
- const char *full_name;
-
- struct property *properties;
- struct property *deadprops;
- struct device_node *parent;
- struct device_node *child;
- struct device_node *sibling;
- struct device_node *next;
- struct device_node *allnext;
- struct proc_dir_entry *pde;
- struct kref kref;
- unsigned long _flags;
- void *data;
- #if defined(CONFIG_SPARC)
- const char *path_component_name;
- unsigned int unique_id;
- struct of_irq_controller *irq_trans;
- #endif
- };
1.7 属性结构体
- struct property {
- char *name;
- int length;
- void *value;
- struct property *next;
- unsigned long _flags;
- unsigned int unique_id;
- };
三、设备树初始化及解析
分析Linux内核的源码,可以看到其对扁平设备树的解析流程如下:
(1)首先在内核入口处将从u-boot传递过来的镜像基地址。
(2)通过调用early_init_dt_scan()函数来获取内核前期初始化所需的bootargs,cmd_line等系统引导参数。
(3)根据bootargs,cmd_line等系统引导参数进入start_kernel()函数,进行内核的第二阶段初始化。
(4)调用unflatten_device_tree()函数来解析dtb文件,构建一个由device_node结构连接而成的单项链表,并使用全局变量of_allnodes指针来保存这个链表的头指针。
(5)内核调用OF提供的API函数获取of_allnodes链表信息来初始化内核其他子系统、设备等。
-
- asmlinkage void __init start_kernel(void)
- {
- ...
-
- setup_arch(&command_line);
- ...
- }
-
- void __init setup_arch(char **cmdline_p)
- {
- const struct machine_desc *mdesc;
-
- setup_processor();
-
- mdesc = setup_machine_fdt(__atags_pointer);
- if (!mdesc)
- mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
- machine_desc = mdesc;
- machine_name = mdesc->name;
-
- if (mdesc->reboot_mode != REBOOT_HARD)
- reboot_mode = mdesc->reboot_mode;
-
- init_mm.start_code = (unsigned long) _text;
- init_mm.end_code = (unsigned long) _etext;
- init_mm.end_data = (unsigned long) _edata;
- init_mm.brk = (unsigned long) _end;
-
- strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
- *cmdline_p = cmd_line;
-
- parse_early_param();
-
- sort(&meminfo.bank, meminfo.nr_banks, sizeof(meminfo.bank[0]), meminfo_cmp, NULL);
-
- early_paging_init(mdesc, lookup_processor_type(read_cpuid_id()));
- setup_dma_zone(mdesc);
- sanity_check_meminfo();
- arm_memblock_init(&meminfo, mdesc);
-
- paging_init(mdesc);
- request_standard_resources(mdesc);
-
- if (mdesc->restart)
- arm_pm_restart = mdesc->restart;
-
-
- unflatten_device_tree();
-
- ......
- }</span>
(一) 函数获取内核前期初始化所需的bootargs,cmd_line等系统引导参数
1. setup_machine_fdt()函数获取内核前期初始化所需的bootargs,cmd_line等系统引导参数。
- const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
- {
- const struct machine_desc *mdesc, *mdesc_best = NULL;
-
- #ifdef CONFIG_ARCH_MULTIPLATFORM
- DT_MACHINE_START(GENERIC_DT, "Generic DT based system")
- MACHINE_END
-
- mdesc_best = &__mach_desc_GENERIC_DT;
- #endif
-
-
-
- if (!dt_phys || !early_init_dt_scan(phys_to_virt(dt_phys)))
- return NULL;
-
-
- mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
- if (!mdesc) {
- const char *prop;
- long size;
- unsigned long dt_root;
-
- early_print("\nError: unrecognized/unsupported ""device tree compatible list:\n[ ");
-
-
- dt_root = of_get_flat_dt_root();
-
- prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
-
- while (size > 0) {
- early_print("'%s' ", prop);
- size -= strlen(prop) + 1;
- prop += strlen(prop) + 1;
- }
- early_print("]\n\n");
-
- dump_machine_table();
- }
-
-
- __machine_arch_type = mdesc->nr;
-
- return mdesc;
- }
-
- struct boot_param_header *initial_boot_params;
- bool __init early_init_dt_scan(void *params)
- {
- if (!params)
- return false;
-
-
- initial_boot_params = params;
-
-
- if (be32_to_cpu(initial_boot_params->magic) != OF_DT_HEADER) {
- initial_boot_params = NULL;
- return false;
- }
-
-
- of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
-
-
- of_scan_flat_dt(early_init_dt_scan_root, NULL);
-
-
- of_scan_flat_dt(early_init_dt_scan_memory, NULL);
-
- return true;
- }
-
- int __init of_scan_flat_dt(int (*it)(unsigned long node,const char *uname, int depth,void *data),void *data)
- {
-
- unsigned long p = ((unsigned long)initial_boot_params) + be32_to_cpu(initial_boot_params->off_dt_struct);
- int rc = 0;
- int depth = -1;
-
- do {
-
- u32 tag = be32_to_cpup((__be32 *)p);
- const char *pathp;
-
- p += 4;
-
-
- if (tag == OF_DT_END_NODE) {
- depth--;
- continue;
- }
-
-
- if (tag == OF_DT_NOP)
- continue;
-
-
- if (tag == OF_DT_END)
- break;
-
-
- if (tag == OF_DT_PROP) {
-
- u32 sz = be32_to_cpup((__be32 *)p);
- p += 8;
- if (be32_to_cpu(initial_boot_params->version) < 0x10)
- p = ALIGN(p, sz >= 8 ? 8 : 4);
-
- p += sz;
-
- p = ALIGN(p, 4);
-
- continue;
- }
-
-
- if (tag != OF_DT_BEGIN_NODE) {
- pr_err("Invalid tag %x in flat device tree!\n", tag);
- return -EINVAL;
- }
-
-
- depth++;
-
- pathp = (char *)p;
-
- p = ALIGN(p + strlen(pathp) + 1, 4);
-
- if (*pathp == '/')
- pathp = kbasename(pathp);
-
- rc = it(p, pathp, depth, data);
- if (rc != 0)
- break;
- } while (1);
-
- return rc;
- }
1.1 chosen节点
chosen 节点并不代表一个真实的设备,只是作为一个为固件和操作系统之间传递数据的地方,比如引导参数。chosen 节点里的数据也不代表硬件。通常,chosen 节点在.dts 源文件中为空,并在启动时填充。在我们的示例系统中,固件可以往 chosen 节点添加以下信息:
chosen {
bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200"; //节点属性
linux,initrd-start = <0x85500000>; //节点属性
linux,initrd-end = <0x855a3212>; //节点属性
};
- int __init early_init_dt_scan_chosen(unsigned long node, const char *uname,int depth, void *data)
- {
- unsigned long l;
- char *p;
-
- pr_debug("search \"chosen\", depth: %d, uname: %s\n", depth, uname);
-
-
-
-
- if (depth != 1 || !data || (strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0))
- return 0;
-
-
- early_init_dt_check_for_initrd(node);
-
-
- p = of_get_flat_dt_prop(node, "bootargs", &l);
- if (p != NULL && l > 0)
- strlcpy(data, p, min((int)l, COMMAND_LINE_SIZE));
-
- pr_debug("Command line is: %s\n", (char*)data);
-
- return 1;
- }
-
- static void __init early_init_dt_check_for_initrd(unsigned long node)
- {
- u64 start, end;
- unsigned long len;
- __be32 *prop;
-
- pr_debug("Looking for initrd properties... ");
-
-
- prop = of_get_flat_dt_prop(node, "linux,initrd-start", &len);
- if (!prop)
- return;
-
- start = of_read_number(prop, len/4);
-
-
- prop = of_get_flat_dt_prop(node, "linux,initrd-end", &len);
- if (!prop)
- return;
-
- end = of_read_number(prop, len/4);
-
-
- initrd_start = (unsigned long)__va(start);
- initrd_end = (unsigned long)__va(end);
- initrd_below_start_ok = 1;
-
- pr_debug("initrd_start=0x%llx initrd_end=0x%llx\n",(unsigned long long)start, (unsigned long long)end);
- }
-
- void *__init of_get_flat_dt_prop(unsigned long node, const char *name,unsigned long *size)
- {
- return of_fdt_get_property(initial_boot_params, node, name, size);
- }
-
- void *of_fdt_get_property(struct boot_param_header *blob,unsigned long node, const char *name,unsigned long *size)
- {
-
- unsigned long p = node;
-
-
- do {
-
- u32 tag = be32_to_cpup((__be32 *)p);
- u32 sz, noff;
- const char *nstr;
-
- p += 4;
-
-
- if (tag == OF_DT_NOP)
- continue;
-
- if (tag != OF_DT_PROP)
- return NULL;
-
-
-
- sz = be32_to_cpup((__be32 *)p);
-
- noff = be32_to_cpup((__be32 *)(p + 4));
- p += 8;
-
- if (be32_to_cpu(blob->version) < 0x10)
- p = ALIGN(p, sz >= 8 ? 8 : 4);
-
-
- nstr = of_fdt_get_string(blob, noff);
- if (nstr == NULL) {
- pr_warning("Can't find property index name !\n");
- return NULL;
- }
-
-
- if (strcmp(name, nstr) == 0) {
- if (size)
- *size = sz;
-
- return (void *)p;
- }
-
- p += sz;
- p = ALIGN(p, 4);
- } while (1);
- }
-
- char *of_fdt_get_string(struct boot_param_header *blob, u32 offset)
- {
-
- return ((char *)blob) + be32_to_cpu(blob->off_dt_strings) + offset;
- }
-
- static inline u64 of_read_number(const __be32 *cell, int size)
- {
- u64 r = 0;
-
- while (size--)
- r = (r << 32) | be32_to_cpu(*(cell++));
- return r;
- }
1.2 根节点"/"
设备树有且仅有一个根节点,即“/”,根节点下包含很多子节点,例入下图,根节点为"/",根节点的子节点为"chosen",根节点的属性包含"compatible","#address-cells","#size-cells","interrupt-parent"等。属性model指明了目标板平台或模块的名称,属性compatible值指明和目标板为同一系列的兼容的开发板名称。对于大多数32位平台,属性#address-cells和#size-cells的值一般为1。#address-cells = <1>; 1表示地址32位,2表示地址64位。#size-cells = <1>;1表示rangs的每部分占一个cell,依此类推
{
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>;
};
}
所以本函数就是读取根节点的"#address-cells","#size-cells"属性
- int __init early_init_dt_scan_root(unsigned long node, const char *uname,int depth, void *data)
- {
- __be32 *prop;
-
-
- if (depth != 0)
- return 0;
-
- dt_root_size_cells = OF_ROOT_NODE_SIZE_CELLS_DEFAULT;
- dt_root_addr_cells = OF_ROOT_NODE_ADDR_CELLS_DEFAULT;
-
-
- prop = of_get_flat_dt_prop(node, "#size-cells", NULL);
- if (prop)
- dt_root_size_cells = be32_to_cpup(prop);
-
- pr_debug("dt_root_size_cells = %x\n", dt_root_size_cells);
-
-
- prop = of_get_flat_dt_prop(node, "#address-cells", NULL);
- if (prop)
- dt_root_addr_cells = be32_to_cpup(prop);
- pr_debug("dt_root_addr_cells = %x\n", dt_root_addr_cells);
-
- return 1;
- }
1.3 memory节点
memory节点用于描述目标板上物理内存范围,一般称作/memory节点,可以有一个或多个。当有多个节点时,需要后跟单元地址予以区分;只有一个单元地址时,可以不写单元地址,默认为0。此节点包含板上物理内存的属性,一般要指定device_type(固定为"memory")和reg属性。其中reg的属性值以<起始地址 空间大小>的形式给出,如下示例中目标板内存起始地址为0x80000000,大小为0x20000000字节。
memory {
device_type = "memory";
reg = <0x80000000 0x20000000>;
};
- int __init early_init_dt_scan_memory(unsigned long node, const char *uname,int depth, void *data)
- {
-
- char *type = of_get_flat_dt_prop(node, "device_type", NULL);
- __be32 *reg, *endp;
- unsigned long l;
-
-
- if (type == NULL) {
- if (depth != 1 || strcmp(uname, "memory@0") != 0)
- return 0;
- } else if (strcmp(type, "memory") != 0)
- return 0;
-
-
- reg = of_get_flat_dt_prop(node, "linux,usable-memory", &l);
- if (reg == NULL)
- reg = of_get_flat_dt_prop(node, "reg", &l);
- if (reg == NULL)
- return 0;
-
-
- endp = reg + (l / sizeof(__be32));
-
- pr_debug("memory scan node %s, reg size %ld, data: %x %x %x %x,\n",
- uname, l, reg[0], reg[1], reg[2], reg[3]);
-
- while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {
- u64 base, size;
-
-
- base = dt_mem_next_cell(dt_root_addr_cells, ®);
- size = dt_mem_next_cell(dt_root_size_cells, ®);
-
- if (size == 0)
- continue;
- pr_debug(" - %llx , %llx\n", (unsigned long long)base,(unsigned long long)size);
-
-
- early_init_dt_add_memory_arch(base, size);
- }
-
- return 0;
- }
2. 通过比较根节点属性compatible值指明和目标板为同一系列的兼容的开发板名称
compatible制定系统的名称。它包含"<manufacture>,<model>"格式的字符串。准确地确定器件型号是非常重要的,并且我们需要包含厂商的名字来避免名字空间冲突。因为操作系统会使用compatible这个值来决定怎样在这个机器上运行,所以在这个属性中放入正确的值是非常重要的。
- const void * __init of_flat_dt_match_machine(const void *default_match,
- const void * (*get_next_compat)(const char * const**))
- {
- const void *data = NULL;
- const void *best_data = default_match;
- const char *const *compat;
- unsigned long dt_root;
- unsigned int best_score = ~1, score = 0;
-
-
- dt_root = of_get_flat_dt_root();
-
-
- while ((data = get_next_compat(&compat))) {
-
- score = of_flat_dt_match(dt_root, compat);
-
- if (score > 0 && score < best_score) {
- best_data = data;
- best_score = score;
- }
- }
-
- if (!best_data) {
- const char *prop;
- long size;
-
- pr_err("\n unrecognized device tree list:\n[ ");
-
- prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
- if (prop) {
- while (size > 0) {
- printk("'%s' ", prop);
- size -= strlen(prop) + 1;
- prop += strlen(prop) + 1;
- }
- }
- printk("]\n\n");
- return NULL;
- }
-
- pr_info("Machine model: %s\n", of_flat_dt_get_machine_name());
-
- return best_data;
- }
-
-
-
- unsigned long __init of_get_flat_dt_root(void)
- {
-
- unsigned long p = ((unsigned long)initial_boot_params) +
- be32_to_cpu(initial_boot_params->off_dt_struct);
-
-
- while (be32_to_cpup((__be32 *)p) == OF_DT_NOP)
- p += 4;
- BUG_ON(be32_to_cpup((__be32 *)p) != OF_DT_BEGIN_NODE);
- p += 4;
-
- return ALIGN(p + strlen((char *)p) + 1, 4);
- }
-
-
- __arch_info_begin 和 __arch_info_end是在 arch/arm/kernel/vmlinux.lds.S中:
- 00034: __arch_info_begin = .;
- 00035: *(.arch.info.init)
- 00036: __arch_info_end = .;
- 这里是声明了两个变量:__arch_info_begin 和 __arch_info_end,其中等号后面的"."是location counter。在__arch_info_begin 的位置上,放置所有文件中的 ".arch.info.init" 段的内容,然后紧接着是 __arch_info_end 的位置.".arch.info.init" 段中定义了设备的machine_desc结构。
-
- static const void * __init arch_get_next_mach(const char *const **match)
- {
- static const struct machine_desc *mdesc = __arch_info_begin;
- const struct machine_desc *m = mdesc;
-
- if (m >= __arch_info_end)
- return NULL;
-
- mdesc++;
- *match = m->dt_compat;
- return m;
- }
-
-
- int __init of_flat_dt_match(unsigned long node, const char *const *compat)
- {
-
-
-
- return of_fdt_match(initial_boot_params, node, compat);
- }
-
- int of_fdt_match(struct boot_param_header *blob, unsigned long node,const char *const *compat)
- {
- unsigned int tmp, score = 0;
-
- if (!compat)
- return 0;
-
-
- while (*compat) {
-
- tmp = of_fdt_is_compatible(blob, node, *compat);
- if (tmp && (score == 0 || (tmp < score)))
- score = tmp;
- compat++;
- }
-
- return score;
- }
-
- int of_fdt_is_compatible(struct boot_param_header *blob,unsigned long node, const char *compat)
- {
- const char *cp;
- unsigned long cplen, l, score = 0;
-
-
- cp = of_fdt_get_property(blob, node, "compatible", &cplen);
- if (cp == NULL)
- return 0;
-
-
- while (cplen > 0) {
- score++;
- if (of_compat_cmp(cp, compat, strlen(compat)) == 0)
- return score;
- l = strlen(cp) + 1;
- cp += l;
- cplen -= l;
- }
-
- return 0;
- }
(二)、解析设备树
unflatten_device_tree()函数来解析dtb文件,构建一个由device_node结构连接而成的单项链表,并使用全局变量of_allnodes指针来保存这个链表的头指针。内核调用OF提供的API函数获取of_allnodes链表信息来初始化内核其他子系统、设备。
- void __init unflatten_device_tree(void)
- {
-
- __unflatten_device_tree(initial_boot_params, &of_allnodes,early_init_dt_alloc_memory_arch);
-
-
- of_alias_scan(early_init_dt_alloc_memory_arch);
- }
-
- static void __unflatten_device_tree(struct boot_param_header *blob,
- struct device_node **mynodes,
- void * (*dt_alloc)(u64 size, u64 align))
- {
- unsigned long size;
- void *start, *mem;
- struct device_node **allnextp = mynodes;
-
- pr_debug(" -> unflatten_device_tree()\n");
-
- if (!blob) {
- pr_debug("No device tree pointer\n");
- return;
- }
-
- pr_debug("Unflattening device tree:\n");
- pr_debug("magic: %08x\n", be32_to_cpu(blob->magic));
- pr_debug("size: %08x\n", be32_to_cpu(blob->totalsize));
- pr_debug("version: %08x\n", be32_to_cpu(blob->version));
-
-
- if (be32_to_cpu(blob->magic) != OF_DT_HEADER) {
- pr_err("Invalid device tree blob header\n");
- return;
- }
-
-
- start = ((void *)blob) + be32_to_cpu(blob->off_dt_struct);
-
- size = (unsigned long)unflatten_dt_node(blob, 0, &start, NULL, NULL, 0);
- size = ALIGN(size, 4);
-
- pr_debug(" size is %lx, allocating...\n", size);
-
-
- mem = dt_alloc(size + 4, __alignof__(struct device_node));
- memset(mem, 0, size);
-
-
- *(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);
- pr_debug(" unflattening %p...\n", mem);
-
-
- start = ((void *)blob) + be32_to_cpu(blob->off_dt_struct);
-
- unflatten_dt_node(blob, mem, &start, NULL, &allnextp, 0);
- if (be32_to_cpup(start) != OF_DT_END)
- pr_warning("Weird tag at end of tree: %08x\n", be32_to_cpup(start));
- if (be32_to_cpup(mem + size) != 0xdeadbeef)
- pr_warning("End of tree marker overwritten: %08x\n",be32_to_cpup(mem + size));
- *allnextp = NULL;
-
- pr_debug(" <- unflatten_device_tree()\n");
- }
-
- static void * unflatten_dt_node(struct boot_param_header *blob,
- void *mem,void **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(*p);
-
- if (tag != OF_DT_BEGIN_NODE) {
- pr_err("Weird tag at start of node: %x\n", tag);
- return mem;
- }
-
- *p += 4;
- pathp = *p;
- l = allocl = strlen(pathp) + 1;
- *p = PTR_ALIGN(*p + l, 4);
-
-
- if ((*pathp) != '/') {
- new_format = 1;
- if (fpsize == 0) {
- fpsize = 1;
- allocl = 2;
- l = 1;
- *pathp = '\0';
- } else {
- fpsize += l;
- allocl = fpsize;
- }
- }
-
-
- np = unflatten_dt_alloc(&mem, sizeof(struct device_node) + allocl,__alignof__(struct device_node));
-
-
-
- if (allnextpp) {
- char *fn;
-
- np->full_name = fn = ((char *)np) + sizeof(*np);
-
- if (new_format) {
- if (dad && dad->parent) {
- strcpy(fn, dad->full_name);
- fn += strlen(fn);
- }
- *(fn++) = '/';
- }
- memcpy(fn, pathp, l);
-
-
- prev_pp = &np->properties;
-
-
- **allnextpp = np;
- *allnextpp = &np->allnext;
-
-
- if (dad != NULL) {
- np->parent = dad;
- if (dad->next == NULL)
- dad->child = np;
- else
- dad->next->sibling = np;
- dad->next = np;
- }
- kref_init(&np->kref);
- }
-
-
- while (1) {
- u32 sz, noff;
- char *pname;
-
-
- tag = be32_to_cpup(*p);
-
- if (tag == OF_DT_NOP) {
- *p += 4;
- continue;
- }
-
- if (tag != OF_DT_PROP)
- break;
-
- *p += 4;
-
- sz = be32_to_cpup(*p);
-
- noff = be32_to_cpup(*p + 4);
-
- *p += 8;
-
- if (be32_to_cpu(blob->version) < 0x10)
- *p = PTR_ALIGN(*p, sz >= 8 ? 8 : 4);
-
-
- pname = of_fdt_get_string(blob, noff);
- if (pname == NULL) {
- pr_info("Can't find property name in list !\n");
- break;
- }
-
-
- if (strcmp(pname, "name") == 0)
- has_name = 1;
-
- l = strlen(pname) + 1;
-
-
-
- pp = unflatten_dt_alloc(&mem, sizeof(struct property),__alignof__(struct property));
-
-
-
- if (allnextpp) {
- if ((strcmp(pname, "phandle") == 0) || (strcmp(pname, "linux,phandle") == 0)) {
- if (np->phandle == 0)
- np->phandle = be32_to_cpup((__be32*)*p);
- }
- if (strcmp(pname, "ibm,phandle") == 0)
- np->phandle = be32_to_cpup((__be32 *)*p);
- pp->name = pname;
- pp->length = sz;
- pp->value = *p;
-
-
- *prev_pp = pp;
- prev_pp = &pp->next;
- }
- *p = PTR_ALIGN((*p) + sz, 4);
- }
-
-
-
- if (!has_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__(struct property));
- 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);
- ((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) {
-
- if (tag == OF_DT_NOP)
- *p += 4;
- else
-
- mem = unflatten_dt_node(blob, mem, p, np, allnextpp,fpsize);
- tag = be32_to_cpup(*p);
- }
-
-
- if (tag != OF_DT_END_NODE) {
- pr_err("Weird tag at end of node: %x\n", tag);
- return mem;
- }
- *p += 4;
-
- return mem;
- }
-
-
-
- static void *unflatten_dt_alloc(void **mem, unsigned long size,unsigned long align)
- {
- void *res;
-
- *mem = PTR_ALIGN(*mem, align);
- res = *mem;
- *mem += size;
-
- return res;
- }
-
-
-
-
-
-
-
-
-
-
-
-
- void of_alias_scan(void * (*dt_alloc)(u64 size, u64 align))
- {
- struct property *pp;
-
-
- of_chosen = of_find_node_by_path("/chosen");
- if (of_chosen == NULL)
- of_chosen = of_find_node_by_path("/chosen@0");
-
-
-
- if (of_chosen) {
- const char *name;
-
- name = of_get_property(of_chosen, "linux,stdout-path", NULL);
-
- if (name)
- of_stdout = of_find_node_by_path(name);
- }
-
-
- of_aliases = of_find_node_by_path("/aliases");
- if (!of_aliases)
- return;
-
-
- for_each_property_of_node(of_aliases, pp) {
- const char *start = pp->name;
- const char *end = start + strlen(start);
- struct device_node *np;
- struct alias_prop *ap;
- int id, len;
-
-
- if (!strcmp(pp->name, "name") ||
- !strcmp(pp->name, "phandle") ||
- !strcmp(pp->name, "linux,phandle"))
- continue;
-
-
- np = of_find_node_by_path(pp->value);
- if (!np)
- continue;
-
-
- while (isdigit(*(end-1)) && end > start)
- end--;
-
- len = end - start;
-
-
-
-
- if (kstrtoint(end, 10, &id) < 0)
- continue;
-
-
- ap = dt_alloc(sizeof(*ap) + len + 1, 4);
- if (!ap)
- continue;
- memset(ap, 0, sizeof(*ap) + len + 1);
- ap->alias = start;
-
- of_alias_add(ap, np, id, start, len);
- }
- }
四、OF提供的常用API函数
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("/