转自:
ARM Device Tree起源于OpenFirmware (OF),在过去的中,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来获取。
struct nr;
- *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 设备节点结构体
struct *name;
- *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 属性结构体
struct *name;
- 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节点
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 根节点"/"
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;
- }
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/ethernet@0,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);
- }