kernel版本:4.4.143
内核文档usage-model.txt对设备树的3个用途做了总结:
- platform identification //平台鉴别
- runtime configuration //运行时配置
- device population //设备填充
1. platform identification处理
--> start_kernel(void) //kernel\init\Main.c
----> setup_arch(&command_line) \\kernel\arch\arm\kernel\Setup.c
------> mdesc = setup_machine_fdt(__atags_pointer) //__atags_pointer为bootloader传递给内核的dtb在内存中的物理地址
--------> early_init_dt_verify(phys_to_virt(__atags_pointer)
----------> initial_boot_params = __atags_pointer; //initial_boot_params为全局变量,将会被接下来用到
--------> mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach); //找到设备树中与内核代码中定义相匹配的machine_desc,
//逐个取出内核特定代码段中的machine_desc,然后逐个取出machine_desc.dt_compat中的元素和设备树根结点的compatible
//各个值比较一遍,一但某个machine_desc.dt_compat中的元素和设备树根结点的compatible的某个值匹配,
//mdesc就等于这个匹配上的machine_desc
内核所有machine_desc都放在.arch.info.init代码段中
例如内核(arch/arm/mach-rockchip/rockchip.c)中定义了
DT_MACHINE_START(ROCKCHIP_DT, "Rockchip (Device Tree)")
.l2c_aux_val = 0,
.l2c_aux_mask = ~0,
.init_time = rockchip_timer_init,
.dt_compat = rockchip_board_dt_compat,
.init_machine = rockchip_dt_init,
MACHINE_END
展开宏为
static const struct machine_desc __mach_desc_ROCKCHIP_DT __used __attribute__((__section__(".arch.info.init"))) = {
.nr = ~0,
.name = "Rockchip (Device Tree)",
.init_time = rockchip_timer_init,
.dt_compat = rockchip_board_dt_compat,
.init_machine = rockchip_dt_init,
};
static const char * const rockchip_board_dt_compat[] = {
"rockchip,rk2928",
"rockchip,rk3066a",
"rockchip,rk3066b",
"rockchip,rk3188",
"rockchip,rk3288",
NULL,
};
最终rockchip_board_dt_compat中的 "rockchip,rk3288"会和compatible = "firefly,firefly-rk3288", "rockchip,rk3288";匹配
2.runtime configuration处理
--------> early_init_dt_scan_nodes()
在setup_machine_fdt(unsigned int dt_phys)中处理完machine_desc信息,
紧接着会通过early_init_dt_scan_nodes()函数处理platform identification信息,
platform identification信息为chosen节点下bootargs、根结点下#size-cells和#address-cells、
memory节点信息,memory节点下有device_type = "memory"才是有效的memory节点
void __init early_init_dt_scan_nodes(void)
{
/* Retrieve various information from the /chosen node */
of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line); //解析设备树chosen(chosen@0)节点下的bootargs属性,并赋值给全局变量boot_command_line
/* Initialize {size,address}-cells info */
of_scan_flat_dt(early_init_dt_scan_root, NULL); //解析设备树根节点#size-cells和#address-cells,并分别赋值给全局变量dt_root_size_cells 和dt_root_addr_cells
/* Setup memory, calling early_init_dt_add_memory_arch */
of_scan_flat_dt(early_init_dt_scan_memory, NULL); //解析设备树中有属性为device_type = "memory"的节点,读取内存信息,最终会向系统添加一个内存块
}
--> early_init_dt_scan_memory(unsigned long node, const char *uname, int depth, void *data)
----> early_init_dt_add_memory_arch(u64 base, u64 size)
------> memblock_add(phys_addr_t base, phys_addr_t size) //添加一个内存块
3.device population处理
------> unflatten_device_tree();
--------> __unflatten_device_tree(initial_boot_params, &of_root, early_init_dt_alloc_memory_arch); //initial_boot_params指向uboot传过来的dtb地址,of_root将会指向由dtb展开的device_node根结点
//__unflatten_device_tree 中两次调用unflatten_dt_node
----------> size = (unsigned long)unflatten_dt_node(blob, NULL, &start, NULL, NULL, 0, true); //计算所有将展开的device_node所需内存空间大小
----------> unflatten_dt_node(blob, mem, &start, NULL, mynodes, 0, false); //mynodes(of_root)指向展开的device_node根节点
//下面以解析设备树为例剖析unflatten_dt_node函数
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;
pathp = fdt_get_name(blob, *poffset, &l);//得到device_node 名字和名字长度
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;
}
}
np = unflatten_dt_alloc(&mem, sizeof(struct device_node) + allocl, //申请一个device_node 结构体+节点名字长度的内存空间
__alignof__(struct device_node));
if (!dryrun) {
char *fn;
of_node_init(np);
np->full_name = fn = ((char *)np) + sizeof(*np); //device_node的full_name指向节点结构体尾部
if (new_format) {
/* rebuild full path for new format */
if (dad && dad->parent) { //如果是根device_node 的子device_node的子device_node及以下device_node,该device_node full_name需要加上父device_node 名字
strcpy(fn, dad->full_name);
#ifdef DEBUG
if ((strlen(fn) + l + 1) != allocl) {
pr_debug("%s: p: %d, l: %d, a: %d\n",
pathp, (int)strlen(fn),
l, allocl);
}
#endif
fn += strlen(fn);
}
*(fn++) = '/';
}
memcpy(fn, pathp, l); //加上该device_node 名称,等于从根device_node 起的全名
prev_pp = &np->properties;
if (dad != NULL) {
np->parent = dad;
np->sibling = dad->child;
dad->child = np;
}
}
/* process properties */
for (offset = fdt_first_property_offset(blob, *poffset);
(offset >= 0);
(offset = fdt_next_property_offset(blob, offset))) { //解析device_node下所有property,并填充为property结构体链表
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. */
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
*/
if (!has_name) { //如果device_node没有name属性,则补充,补充内容为device_node名称@符号前的内容
const 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 (!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;
pr_debug("fixed up name for %s -> %s\n", pathp,
(char *)pp->value);
}
}
if (!dryrun) { //如果device_node 有name和device_type属性,则填充之
*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>";
}
old_depth = depth;
*poffset = fdt_next_node(blob, *poffset, &depth);
if (depth < 0)
depth = 0;
while (*poffset > 0 && depth > old_depth)
mem = unflatten_dt_node(blob, mem, poffset, np, NULL,
fpsize, dryrun); //递归调用解析完所有device_node
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;
}
//下面看一下device_node和property两个结构体
struct device_node {
const char *name; //设备树节点存在name属性,则等于name值,否则等于设备树节点名@符号前面的内容
const char *type; //等于属性名为device_type的值,不存在device_type等于"<NULL>"
phandle phandle;
const char *full_name; //从根结点开始的全名
struct fwnode_handle fwnode;
struct property *properties;
struct property *deadprops; /* removed properties */
struct device_node *parent;
struct device_node *child;
struct device_node *sibling; //兄弟device_node
struct kobject kobj;
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
};
struct property {
char *name; //属性名称
int length; //属性值的长度
void *value; //属性的值
struct property *next; //下一个属性
unsigned long _flags;
unsigned int unique_id;
struct bin_attribute attr;
};
--------> of_alias_scan(early_init_dt_alloc_memory_arch);
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 = of_get_property(of_chosen, "stdout-path", NULL);
if (!name)
name = of_get_property(of_chosen, "linux,stdout-path", NULL);
if (IS_ENABLED(CONFIG_PPC) && !name)
name = of_get_property(of_aliases, "stdout", NULL);
if (name)
of_stdout = of_find_node_opts_by_path(name, &of_stdout_options); //of_stdout指向/chosen节点下*stdout*属性
//所代表的标准输出,of_stdout_options等于/chosen
//节点下*stdout*属性表明的参数,
//例如chosen { stdout-path = "serial2:115200n8"; };
//of_stdout指向serial2节点,of_stdout_options="115200n8"
}
if (!of_aliases)
return;
for_each_property_of_node(of_aliases, pp) { //解析of_aliases节点下所有属性
const char *start = pp->name;
const char *end = start + strlen(start);
struct device_node *np;
struct alias_prop *ap;
int id, len;
/* Skip those we do not want to proceed */
if (!strcmp(pp->name, "name") ||
!strcmp(pp->name, "phandle") ||
!strcmp(pp->name, "linux,phandle"))
continue;
np = of_find_node_by_path(pp->value); //获取pp->value对应的device_node
if (!np)
continue;
/* walk the alias backwards to extract the id and work out
* the 'stem' string */
while (isdigit(*(end-1)) && end > start) //例如属性名字为“i2c0”,start指向'i',end指向'0'
end--;
len = end - start; //等于"i2c"长度
if (kstrtoint(end, 10, &id) < 0)//把'0'转换成十进制的数字,赋值给id
continue;
/* Allocate an alias_prop with enough space for the stem */
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_alias_add
static void of_alias_add(struct alias_prop *ap, struct device_node *np,
int id, const char *stem, int stem_len)
{
ap->np = np;
ap->id = id;
strncpy(ap->stem, stem, stem_len);
ap->stem[stem_len] = 0;
list_add_tail(&ap->link, &aliases_lookup); //把ap放入aliases_lookup链表
pr_debug("adding DT alias:%s: stem=%s id=%i node=%s\n",
ap->alias, ap->stem, ap->id, of_node_full_name(np));
}
}
struct alias_prop {
struct list_head link;
const char *alias; //指向对应property的开头地址
struct device_node *np; //指向代表i2c0的device_node
int id; //等于0
char stem[0]; //等于"i2c"
};
aliases { //截取dts中的aliases节点
i2c0 = &i2c0;
i2c1 = &i2c1;
};
4.aliases_lookup的使用
以i2c举例
kernel\drivers\i2c\busses\I2c-rk3x.c
static int rk3x_i2c_probe(struct platform_device *pdev)
{
......
int bus_nr;
......
/* Try to set the I2C adapter number from dt */
bus_nr = of_alias_get_id(np, "i2c");
......
}
在i2c-core.c里注册i2c adapter
int i2c_add_adapter(struct i2c_adapter *adapter)
{
struct device *dev = &adapter->dev;
int id;
if (dev->of_node) {
id = of_alias_get_id(dev->of_node, "i2c");
if (id >= 0) {
adapter->nr = id;
return __i2c_add_numbered_adapter(adapter);
}
}
mutex_lock(&core_lock);
id = idr_alloc(&i2c_adapter_idr, adapter,
__i2c_first_dynamic_bus_num, 0, GFP_KERNEL);
mutex_unlock(&core_lock);
if (id < 0)
return id;
adapter->nr = id;
return i2c_register_adapter(adapter);
}
5. /memreserve/ 节点作用
start_kernel // init/main.c
setup_arch(&command_line); // arch/arm/kernel/setup.c
arm_memblock_init(mdesc); // arch/arm/kernel/setup.c
early_init_fdt_reserve_self();
/* Reserve the dtb region */
// 把DTB所占区域保留下来, 即调用: memblock_reserve
early_init_dt_reserve_memory_arch(__pa(initial_boot_params),
fdt_totalsize(initial_boot_params),
0);
early_init_fdt_scan_reserved_mem(); // 根据dtb中的memreserve信息, 调用memblock_reserve
摘自韦东山设备树详细分析