首先来看看root节点解析大体调用流程

这里只关注具体的解析函数的实现,中间的调用流程可以看上图自行跟踪。这里从unflatten_device_tree函数调用开始分析其实现:
unflatten_device_tree
void __init unflatten_device_tree(void)
{
__unflatten_device_tree(initial_boot_params, NULL, &of_root,
early_init_dt_alloc_memory_arch, false);
of_alias_scan(early_init_dt_alloc_memory_arch);
unittest_unflatten_overlay_base();
}
这个函数主要有两点:
1、具体的解析root节点
2、解析节点别名和向内核传递参数
__unflatten_device_tree

由上述可知unflatten_dt_nodes此函数是具体的解析函数,被调用了两次。一次传递的是NULL用以获取设备树信息的总大小,二次才是根据第一次获取大小分配内存保存数据。下面来具体的分配此函数的实现
unflatten_dt_nodes
static int unflatten_dt_nodes(const void *blob,
void *mem,
struct device_node *dad,
struct device_node **nodepp)
{
struct device_node *root;
int offset = 0, depth = 0, initial_depth = 0;
#define FDT_MAX_DEPTH 64 //支持的最大节点深度为64
struct device_node *nps[FDT_MAX_DEPTH];
void *base = mem;
bool dryrun = !base;
int ret;
if (nodepp)
*nodepp = NULL;
if (dad)
depth = initial_depth = 1;
root = dad;
nps[depth] = dad;
//使用广度优先,优先解析相邻节点
for (offset = 0;
offset >= 0 && depth >= initial_depth;
offset = fdt_next_node(blob, offset, &depth)) {
if (WARN_ON_ONCE(depth >= FDT_MAX_DEPTH - 1))
continue;
//节点status不为oa/okay跳过
if (!IS_ENABLED(CONFIG_OF_KOBJ) &&
!of_fdt_device_is_available(blob, offset))
continue;
//具体的解析函数
ret = populate_node(blob, offset, &mem, nps[depth],
&nps[depth+1], dryrun);
if (ret < 0)
return ret;
if (!dryrun && nodepp && !*nodepp)
*nodepp = nps[depth+1];//保存根节点of_root
if (!dryrun && !root)
root = nps[depth+1];//
}
if (offset < 0 && offset != -FDT_ERR_NOTFOUND) {
pr_err("Error %d processing FDT\n", offset);
return -EINVAL;
}
/*
* Reverse the child list. Some drivers assumes node order matches .dts
* node order
*/
if (!dryrun)
reverse_nodes(root);
return mem - base;
}
populate_node

插入操作可以用下图来理解:

插入前

插入后
从上图可以这里的结构相当单链表,只不过所有的子节点都会指向父点。父节点只需拿到child节点,就可以轮询所有的子节点。以of_root根节点为首,就可以遍历到所有的子节点。
下面再来看看,当前节点的属性解析
populate_properties

这里需要注意的是链表的插入操作,一直使用pprev变量取最尾部结构体成员next的地址,链表尾部的成员next肯定为NULL,但其地址不为NULL。插入操作是将地址的内容指向当前的的pp,基本就是单链表尾部插入。大体示意图如下:

到这里device_node的节点解析以及节点里面的属性解析都已经走完了,剩下的就是重复操作直到将整个DTS解析完成。
of_alias_scan
void of_alias_scan(void * (*dt_alloc)(u64 size, u64 align))
{
struct property *pp;
of_aliases = of_find_node_by_path("/aliases");
of_chosen = of_find_node_by_path("/chosen");
if (of_chosen == NULL)
of_chosen = of_find_node_by_path("/chosen@0");
if (of_chosen) {
/* linux,stdout-path and /aliases/stdout are for legacy compatibility */
const char *name = NULL;
if (of_property_read_string(of_chosen, "stdout-path", &name))
of_property_read_string(of_chosen, "linux,stdout-path",
&name);
if (IS_ENABLED(CONFIG_PPC) && !name)
of_property_read_string(of_aliases, "stdout", &name);
if (name)
of_stdout = of_find_node_opts_by_path(name, &of_stdout_options);
if (of_stdout)
of_stdout->fwnode.flags |= FWNODE_FLAG_BEST_EFFORT;
}
if (!of_aliases)
return;
for_each_property_of_node(of_aliases, pp) {
const char *start = pp->name;
这里主要解析了两个比较特别的属性/aliases和/chosen,其中/aliases是节点别名。/chosen配置一些内核参数 比如cmdline。拿到/aliases后会解析其所有属性并添加到全局链表aliases_lookup上。
但是这里好像有个问题就是上面这两个属性都是通过of_find_node_by_path函数赋值的,那么of_find_node_by_path这个函数到底能不能实现上面两个节点的赋值呢,下面来看看这个函数的具体实现:
static inline struct device_node *of_find_node_by_path(const char *path)
{
return of_find_node_opts_by_path(path, NULL);
}

从上图可以得知:of_aliases = of_find_node_by_path(“/aliases”);得到的数据应该是NULL,或者说使用of_aliases = of_find_node_by_path(“/aliases”);
这里来看个具体的aliases定义:

for_each_property_of_node(of_aliases, pp) {
if (strlen(pp->name) == len && !strncmp(pp->name, path, len)) { //如上图所示这里拿到的value值就是具体的节点,这里居然再次调用了of_find_node_by_path,进行嵌套调用,这是不返回NULL不罢休
np = of_find_node_by_path(pp->value);
break;
}
}
本从日常工作中基本没有使用过of_find_node_by_path这个函数,不太清楚这个函数到底有没有问题,能不能实现具体功能。按道理所有的节点信息都会保存在of_root节点,我们直接从root节点拿节点信息就可以了。对上面有疑问的代码改写成以下实现:
of_aliases = of_find_node_by_name(NULL, "/aliases");
of_chosen = of_find_node_by_name(NULL, "/chosen");
for_each_property_of_node(of_aliases, pp) {
if (strlen(pp->name) == len && !strncmp(pp->name, path, len)) {
np = of_find_node_by_name(NULL,pp->value);
break;
}
2万+

被折叠的 条评论
为什么被折叠?



