参考:
https://blog.csdn.net/thisway_diy/article/details/84336817
https://blog.csdn.net/woyimibayi/article/details/77574736
Linux使用设备树完成3个目的:
(1)识别machine(platform identification
);
(2)运行信息提取与管理(runtime configuration
);
(3)生成设备信息(device population
);
函数调用过程
bootloader启动内核时,会设置r0,r1,r2三个寄存器
r0一般设置为0;
r1一般设置为machine id (在使用设备树时该参数没有被使用);
r2一般设置ATAGS或DTB的开始地址;
说明:
(1)machine id,是让内核知道是哪个CPU,从而调用对应的初始化函数。
以前没有使用设备树时,需要bootloader传一个machine id给内核,现在使用设备树的话,这个参数就不需要设置了。
(2)r2是以前的ATAGS开始地址,使用设备树后,是DTB文件开始地址。
uboot将一些参数,设备树文件传给内核,kernel主要完成一下过程:
内核head.S(arch/arm/kernel/head.S)所做工作如下:
(1)__lookup_processor_type : 使用汇编指令读取CPU ID, 根据该ID找到对应的proc_info_list结构体(里面含有这类CPU的初始化函数、信息)
(2)__vet_atags : 判断是否存在可用的ATAGS或DTB
(3)__create_page_tables : 创建页表, 即创建虚拟地址和物理地址的映射关系
(4)__enable_mmu : 使能MMU, 以后就要使用虚拟地址了
(5) __mmap_switched(arch/arm/kernel/head-common.S) : 上述函数里将会调用__mmap_switched
str r9, [r4] @ Save processor ID
str r1, [r5] @ Save machine type
str r2, [r6] @ Save atags pointer
(6)把bootloader传入的r2参数, 保存到变量__atags_pointer中
(7)调用C函数start_kernel
##最终效果
head.S和head-common.S最终效果:
把bootloader传来的r1值, 赋给了C变量: __machine_arch_type
把bootloader传来的r2值,赋给了C变量:__atags_pointer
platform identification
)与运行信息提取管理(runtime configuration
)这一小节包含2部分选择machine_desc(platform identification
)与运行信息提取与管理(runtime configuration
);
platform identification
)在一个内核image中,可以支持多个单板,对于这些单板,都构造了一个machine_desc结构体。
对于JZ2440,它源自smdk2440,内核没有它的单独文件,它使用smdk2440的相关文件,代码。
(1)以前uboot使用ATAGS给内核传参数时,它会传入一个机器ID,内核会使用这个机器ID找到最合适的machine_desc。即机器ID与machine_desc里面的.nr比较,相等就表示找到了对应的machine_desc。
(2)使用DTB文件时,那么这时内核是如何选择对应的machine_desc呢?
model = "SMDK24440";
compatible = "samsung,smdk2440","samsung,smdk24140","samsung,smdk24xx";
compatible属性声明想要什么machine_desc,属性值可以是一系列字符串,依次与machine_desc匹配。
内核最好支持samsung,smdk2440,如果不支持,再尝试是否支持samsung,smdk24140,再不支持,最后尝试samsung,smdk24xx
总结:
a. 设备树根节点的compatible属性列出了一系列的字符串, 表示它兼容的单板名,从"最兼容"到次之;
b. 内核中有多个machine_desc,其中有dt_compat成员, 它指向一个字符串数组, 里面表示该machine_desc支持哪些单板;
c. 使用compatile属性的值, 跟’’‘每一个machine_desc.dt_compat’’'比较,
成绩为"吻合的compatile属性值的位置",
成绩越低越匹配, 对应的machine_desc即被选中
runtime configuration
)对于运行信息的提取及管理,设备树只是起一个信息传递的作用,对这些信息配置的处理,也比较简单,即从设备树的DTB文件中,把这些设备信息提取出来赋给内核中的某个变量即可。
这一部分主要对三种类型的信息进行处理,分别是:
(1)/chosen节点中 bootargs属性;
./chosen节点中bootargs属性就是内核启动的命令行参数,它里面可以指定根文件系统在哪里,第一个运行的应用程序是哪一个,指定内核的打印信息从哪个设备里打印出来。
(2)根节点的 #address-cells 和 #size-cells属性;
根节点的#address-cells和#size-cells属性指定属性参数的位数,比如指定前面memory中的reg属性的地址是32位还是64位,大小是用一个32位表示,还是两个32位表示。
(3)/memory中的 reg属性;
./memory中的reg属性指定了不同板子内存的大小和起始地址。
总结:
a. /chosen节点中bootargs属性的值, 存入全局变量: boot_command_line
b. 确定根节点的这2个属性的值: #address-cells, #size-cells
存入全局变量: dt_root_addr_cells, dt_root_size_cells
c. 解析/memory中的reg属性, 提取出"base, size", 最终调用memblock_add(base, size);
以arm为例进行分析,arm64上有一些差异
内核解析dtb文件并匹配单板,对设备树中运行时配置信息的处理(主要获取三个信息:bootargs信息、#address-cells和 #size-cells 、内存信息)
start_kernel() // arch/arm/kernel/head.S
>>>setup_arch(&command_line); // arch/arm/kernel/setup.c
>>> mdesc = setup_machine_fdt(__atags_pointer);
#uboot以dtb形式传递给kernel,__fdt_pointer(即r2)指向dtb在内存中的首地址(虚拟地址);最后返回的是machine_desc;在函数中还完成了运行信息的提取动作,见下面
>>>early_init_dt_verify(phys_to_virt(dt_phys))// 验证dtb头部是否有效 kernel/drivers/of/fdt.c
>>> mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
#(1)在设备匹配表中匹配最合适的machine
>>>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;
}
}
>>>early_init_dt_scan_nodes();
#(2)解析设备树中的运行时信息
>>> 从/chosen node节点中提取信息command line
of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
>>> 初始化{size,address}-cells信息
of_scan_flat_dt(early_init_dt_scan_root, NULL);
>>> 设置memory
of_scan_flat_dt(early_init_dt_scan_memory, NULL);
>>> machine_desc = mdesc; #最后将选择出来 machine_desc
machine_name = mdesc->name;
device population
)这一部分主要讲解dtb------->struct device_node的过程。struct device_node---->platform_device后面讲解
Device Tree中的每一个node节点经过kernel处理都会生成一个struct device_node的结构体,struct device_node最终一般会被挂接到具体的struct device结构体。
dtb转换为device_node(unflatten)
start_kernel() // arch/arm/kernel/head.S
>>>setup_arch(&command_line); // init/main.c
>>> mdesc = setup_machine_fdt(__atags_pointer);
>>> machine_desc = mdesc;
>>> machine_name = mdesc->name;
>>>arm_memblock_init(mdesc);#为相应的资源预留内存
>>>early_init_fdt_reserve_self();#预留dtb本身占用的内存大小
>>>early_init_fdt_scan_reserved_mem();#fdt中要求保留的内存大小
>>>unflatten_device_tree();#根据dtb创建 struct device_node 树型链表
>>>__unflatten_device_tree(initial_boot_params, NULL, &of_root,
early_init_dt_alloc_memory_arch, false);
#还原设备树,创建struct device_node链表
>>>size = unflatten_dt_nodes(blob, NULL, dad, NULL);
#第一次unflatten,获得dtb所需device_node大小
>>>mem = dt_alloc(size + 4, __alignof__(struct device_node));
#为设备树device_node分配内存
>>> unflatten_dt_nodes(blob, mem, dad, mynodes);
#第二次unflatten,创建device_node,组成device_node链表,还原设备树
>>>of_alias_scan(early_init_dt_alloc_memory_arch);
#获取chosen及aliases节点指针方便后续使用
>>>unittest_unflatten_overlay_base();
其中,unflatten_dt_nodes()函数调用了
fpsizes[depth+1] = populate_node(blob, offset, &mem, nps[depth],
fpsizes[depth], &nps[depth+1], dryrun);
>>>np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl,
__alignof__(struct device_node));//struct device_node分配内存
>>>初始化struct device_node成员
>>>of_node_init(np); //
>>> np->full_name = fn = ((char *)np) + sizeof(*np);
>>> if (dad != NULL) {
np->parent = dad;
np->sibling = dad->child;
dad->child = np;
}
struct device_node *of_root是所有struct device_node的根节点
为了防止内核启动时,占用dtb及dtb要求的memreserve空间,在启动初期,会预留出相应的内存空间。可以看到,先把dtb中的memreserve信息告诉内核,把这块内存区域保留下来,不占用它。
然后将扁平结构的设备树提取出来,构造成一个树,这里涉及两个结构体:device_node结构体和property结构体。
(1)在dts文件里,每个大括号{ }代表一个节点,比如根节点里有个大括号,对应一个device_node结构体;memory也有一个大括号,也对应一个device_node结构体。
(2)节点里面有各种属性,也可能里面还有子节点,所以它们还有一些父子关系。
(3)根节点下的memory、chosen、led等节点是并列关系,兄弟关系。
(4)对于父子关系、兄弟关系,在device_node结构体里面肯定有成员来描述这些关系。
struct device_node结构体描述如下:
struct device_node { const char *name; /* node的名称,取最后一次“/”和“@”之间子串 */ const char *type; /* device_type的属性名称,没有为 phandle phandle; /* phandle属性值 */ const char *full_name; /*指向该结构体结束的位置,存放node的路径全名,例如:/chosen */ struct fwnode_handle fwnode;
struct property *properties; /*指向该节点下的第一个属性,其他属性与该属性链表相接 */ struct property *deadprops; /* removed properties */ struct device_node *parent; /*父节点 */ struct device_node *child; /*子节点 */ struct device_node *sibling; /*姊妹节点,与自己同等级的node */ struct kobject kobj; /* sysfs文件系统目录体现 */ unsigned long _flags; /*当前node状态标志位,见/include/linux/of.h line124-127 */ void *data; };
/* flag descriptions (need to be visible even when !CONFIG_OF) */ #define OF_DYNAMIC 1 /* node and properties were allocated via kmalloc */ #define OF_DETACHED 2 /* node has been detached from the device tree*/ #define OF_POPULATED 3 /* device already created for the node */ #define OF_POPULATED_BUS 4 /* of_platform_populate recursed to children of this node */
|
struct property结构体描述如下:
struct property { char *name; /* property full name */ int length; /* property value length */ void *value; /* property value */ struct property *next; /* next property under the same node */ unsigned long _flags; unsigned int unique_id; struct bin_attribute attr; /*属性文件,与sysfs文件系统挂接 */ };
|
两个结构体与dts内容的对于关系如下:
总的来说,kernel根据Device Tree的文件结构信息转换成struct property结构体,并将同一个node节点下面的所有属性通过property.next指针进行链接,形成一个单链表。
下面具体分析以上信息是如何得来的。Device Tree的解析首先从unflatten_device_tree()开始,然后调用函数__unflatten_device_tree();
(1)参数initial_boot_params指向Device Tree在内存中的首地址;
(2)of_root在经过该函数处理之后,会指向根节点;
(3)early_init_dt_alloc_memory_arch是一个函数指针,为struct device_node和struct property结构体分配内存的回调函数(callback);
在__unflatten_device_tree()函数中,两次调用unflatten_dt_node()函数,第一次是为了得到Device Tree转换成struct device_node和struct property结构体需要分配的内存大小;第二次调用才是具体填充每一个struct device_node和struct property结构体。
unflatten_dt_node()代码列出如下:
/** * unflatten_dt_node - Alloc and populate a device_node from the flat tree * @blob: The parent device tree blob * @mem: Memory chunk to use for allocating device nodes and properties * @poffset: pointer to node in flat tree * @dad: Parent struct device_node * @nodepp: The device_node tree created by the call * @fpsize: Size of the node path up at the current depth. * @dryrun: If true, do not allocate device nodes but still calculate needed * memory size */ static void * unflatten_dt_node(const void *blob, void *mem, int *poffset, struct device_node *dad, struct device_node **nodepp, unsigned long fpsize, bool dryrun) { const __be32 *p; struct device_node *np; struct property *pp, **prev_pp = NULL; const char *pathp; unsigned int l, allocl; static int depth; int old_depth; int offset; int has_name = 0; int new_format = 0;
/* 获取node节点的name指针到pathp中 */ pathp = fdt_get_name(blob, *poffset, &l); if (!pathp) return mem;
allocl = ++l;
/* version 0x10 has a more compact unit name here instead of the full * path. we accumulate the full path size using "fpsize", we'll rebuild * it later. We detect this because the first character of the name is * not '/'. */ 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; l = 1; pathp = ""; } else { /* account for '/' and path size minus terminal 0 * already in 'l' */ fpsize += l; allocl = fpsize; } }
/* 分配struct device_node内存,包括路径全称大小 */ np = unflatten_dt_alloc(&mem, sizeof(struct device_node) + allocl, __alignof__(struct device_node)); if (!dryrun) { char *fn; of_node_init(np);
/* 填充full_name,full_name指向该node节点的全路径名称字符串 */ np->full_name = fn = ((char *)np) + sizeof(*np); if (new_format) { /* rebuild full path for new format */ if (dad && dad->parent) { strcpy(fn, dad->full_name); fn += strlen(fn); } *(fn++) = '/'; } memcpy(fn, pathp, l);
/* 节点挂接到相应的父节点、子节点和姊妹节点 */ prev_pp = &np->properties; if (dad != NULL) { np->parent = dad; np->sibling = dad->child; dad->child = np; } } /* 处理该node节点下面所有的property */ for (offset = fdt_first_property_offset(blob, *poffset); (offset >= 0); (offset = fdt_next_property_offset(blob, offset))) { const char *pname; u32 sz;
if (!(p = fdt_getprop_by_offset(blob, offset, &pname, &sz))) { offset = -FDT_ERR_INTERNAL; break; }
if (pname == NULL) { pr_info("Can't find property name in list !\n"); break; } if (strcmp(pname, "name") == 0) has_name = 1; pp = unflatten_dt_alloc(&mem, sizeof(struct property), __alignof__(struct property)); if (!dryrun) { /* 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. */
/* 处理phandle,得到phandle值 */ if ((strcmp(pname, "phandle") == 0) || (strcmp(pname, "linux,phandle") == 0)) { if (np->phandle == 0) np->phandle = be32_to_cpup(p); } /* And we process the "ibm,phandle" property * used in pSeries dynamic device tree * stuff */ if (strcmp(pname, "ibm,phandle") == 0) np->phandle = be32_to_cpup(p); pp->name = (char *)pname; pp->length = sz; pp->value = (__be32 *)p; *prev_pp = pp; prev_pp = &pp->next; } } /* with version 0x10 we may not have the name property, recreate * it here from the unit name if absent */ /* 为每个node节点添加一个name的属性 */ if (!has_name) { const char *p1 = pathp, *ps = pathp, *pa = NULL; int sz;
/* 属性name的value值为node节点的名称,取“/”和“@”之间的子串 */ 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 (!dryrun) { 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; } } /* 填充device_node结构体中的name和type成员 */ if (!dryrun) { *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 = " if (!np->type) np->type = " }
old_depth = depth; *poffset = fdt_next_node(blob, *poffset, &depth); if (depth < 0) depth = 0; /* 递归调用node节点下面的子节点 */ while (*poffset > 0 && depth > old_depth) mem = unflatten_dt_node(blob, mem, poffset, np, NULL, fpsize, dryrun);
if (*poffset < 0 && *poffset != -FDT_ERR_NOTFOUND) pr_err("unflatten: error %d processing FDT\n", *poffset);
/* * Reverse the child list. Some drivers assumes node order matches .dts * node order */ if (!dryrun && np->child) { struct device_node *child = np->child; np->child = NULL; while (child) { struct device_node *next = child->sibling; child->sibling = np->child; np->child = child; child = next; } }
if (nodepp) *nodepp = np;
return mem; }
|
通过以上函数处理就得到了所有的struct device_node结构体,为每一个node都会自动添加一个名称为“name”的property,property.length的值为当前node的名称取最后一个“/”和“@”之间的子串(包括‘\0’)。例如:/serial@e2900800,则length = 7,property.value = device_node.name = “serial”。