linux root节点解析
首先来看看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 //支持的最大节点深度为64struct 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_rootif (!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;}
