kernel如何根据dtb文件生成device tree
device tree
dtb文件中的内容会被内核组成了device tree,整个tree上由两个数据结构组成:struct device_node和struct property。
struct device_node {
const char *name;
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;
#if defined(CONFIG_OF_KOBJ)
struct kobject kobj;
#endif
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};
struct property {
char *name;
int length;
void *value;
struct property *next;
#if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)
unsigned long _flags;
#endif
#if defined(CONFIG_OF_PROMTREE)
unsigned int unique_id;
#endif
#if defined(CONFIG_OF_KOBJ)
struct bin_attribute attr;
#endif
};
以platform driver注册时为例,也需要找到同一bus下所有的device,依次对比device所对应的device_node中的property信息(一般对比compatible,type以及name信息,参照__of_device_is_compatible函数)。
dtb文件内容
那么dtb文件是如何被组织的呢?其实,主要由三部分组成:struct fdt_header、struct fdt_node_header以及struct fdt_property。
struct fdt_header {
fdt32_t magic; /* magic word FDT_MAGIC */
fdt32_t totalsize; /* total size of DT block */
fdt32_t off_dt_struct; /* offset to structure */
fdt32_t off_dt_strings; /* offset to strings */
fdt32_t off_mem_rsvmap; /* offset to memory reserve map */
fdt32_t version; /* format version */
fdt32_t last_comp_version; /* last compatible version */
/* version 2 fields below */
fdt32_t boot_cpuid_phys; /* Which physical CPU id we're
booting on */
/* version 3 fields below */
fdt32_t size_dt_strings; /* size of the strings block */
/* version 17 fields below */
fdt32_t size_dt_struct; /* size of the structure block */
};
struct fdt_node_header {
fdt32_t tag;
char name[];
};
struct fdt_property {
fdt32_t tag;
fdt32_t len;
fdt32_t nameoff;
char data[];
};
整个dtb文件简单可以分为3大区域(不考虑reserve):header区域、struct区域以及strings区域。
header区域即dtb开头的区域,存放struct fdt_header的数据。
struct区域存放node以及property的节点信息。
strings区域存放property的name信息。
(struct区域因为即存在node,也存在property,因此需要一些tag来区分)。
// node的开始tag
#define FDT_BEGIN_NODE 0x1 /* Start node: full name */
// node的结束tag
#define FDT_END_NODE 0x2 /* End node */
// property的开始tag,因为有len,所以不需要结束tag
#define FDT_PROP 0x3 /* Property: name off,
size, content */
#define FDT_NOP 0x4 /* nop */
#define FDT_END 0x9
访问dtb文件的基础API
tag通常情况下为node或property的开头,或者在读完node或者property之后(需要对齐)的位置上。
- fdt_next_tag:返回当前offset的tag值,并修改nextoffset为下一个tag所在位置:
uint32_t fdt_next_tag(const void *fdt, int offset, int *nextoffset);
2, fdt_next_node:返回下一个node所在位置(如果没有下一个node,则*depth为-1)
int fdt_next_node(const void *fdt, int offset, int *depth)
- nextprop_:常用于查找node的下一个property,如果下一个tag不是property,返回负值。
static int nextprop_(const void *fdt, int offset)
dtb → device tree
以riscv架构为例,dtb文件的解析发生在setup_arch中:
void __init setup_arch(char **cmdline_p)
{
parse_dtb();
setup_initial_init_mm(_stext, _etext, _edata, _end);
*cmdline_p = boot_command_line;
early_ioremap_setup();
sbi_init();
jump_label_init();
parse_early_param();
efi_init();
paging_init();
/* Parse the ACPI tables for possible boot-time configuration */
acpi_boot_table_init();
#if IS_ENABLED(CONFIG_BUILTIN_DTB)
unflatten_and_copy_device_tree();
#else
unflatten_device_tree();
#endif
misc_mem_init();
init_resources();
#ifdef CONFIG_KASAN
kasan_init();
#endif
#ifdef CONFIG_SMP
setup_smp();
#endif
if (!acpi_disabled)
acpi_init_rintc_map();
riscv_init_cbo_blocksizes();
riscv_fill_hwcap();
init_rt_signal_env();
apply_boot_alternatives();
if (IS_ENABLED(CONFIG_RISCV_ISA_ZICBOM) &&
riscv_isa_extension_available(NULL, ZICBOM))
riscv_noncoherent_supported();
}
与dtb相关的是parse_dtb()函数以及unflatten_device_tree()(这里分析CONFIG_BUILTIN_DTB没有ENABLE的情况)。
parse_dtb
static void __init parse_dtb(void)
{
/* Early scan of device tree from init memory */
if (early_init_dt_scan(dtb_early_va)) {
const char *name = of_flat_dt_get_machine_name();
if (name) {
pr_info("Machine model: %s\n", name);
dump_stack_set_arch_desc("%s (DT)", name);
}
} else {
pr_err("No DTB passed to the kernel\n");
}
#ifdef CONFIG_CMDLINE_FORCE
strscpy(boot_command_line, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
pr_info("Forcing kernel command line to: %s\n", boot_command_line);
#endif
}
这里重点是两个函数:early_init_dt_scan以及of_flat_dt_get_machine_name。
early_init_dt_scan函数做一些早期的检查,check_sum(校验)→{size, address}-cells→系统启动参数→将memory节点加入memblock中。
of_flat_get_machine_name去查找了根节点的model属性(调用了前面提到nextprop_接口,实际常用方式为nextprop_的上层封装函数fdt_first_property_offset以及fdt_next_property_offset)。
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);
/* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */
of_alias_scan(early_init_dt_alloc_memory_arch);
unittest_unflatten_overlay_base();
}
核心函数是__unflatten_device_tree,这个函数用于构建device tree。
此时:initial_boot_params为dtb首地址,of_root要生成的设备树根节点,early_init_dt_alloc_memory_arch封装了memblock_alloc(为memblock的内存分配函数)。
__unflatten_device_tree:
void *__unflatten_device_tree(const void *blob,
struct device_node *dad,
struct device_node **mynodes,
void *(*dt_alloc)(u64 size, u64 align),
bool detached)
{
int size;
void *mem;
int ret;
if (mynodes)
*mynodes = NULL;
pr_debug(" -> unflatten_device_tree()\n");
if (!blob) {
pr_debug("No device tree pointer\n");
return NULL;
}
pr_debug("Unflattening device tree:\n");
pr_debug("magic: %08x\n", fdt_magic(blob));
pr_debug("size: %08x\n", fdt_totalsize(blob));
pr_debug("version: %08x\n", fdt_version(blob));
if (fdt_check_header(blob)) {
pr_err("Invalid device tree blob header\n");
return NULL;
}
/* First pass, scan for size */
size = unflatten_dt_nodes(blob, NULL, dad, NULL);
if (size <= 0)
return NULL;
size = ALIGN(size, 4);
pr_debug(" size is %d, allocating...\n", size);
/* Allocate memory for the expanded device tree */
mem = dt_alloc(size + 4, __alignof__(struct device_node));
if (!mem)
return NULL;
memset(mem, 0, size);
*(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);
pr_debug(" unflattening %p...\n", mem);
/* Second pass, do actual unflattening */
ret = unflatten_dt_nodes(blob, mem, dad, mynodes);
if (be32_to_cpup(mem + size) != 0xdeadbeef)
pr_warn("End of tree marker overwritten: %08x\n",
be32_to_cpup(mem + size));
if (ret <= 0)
return NULL;
if (detached && mynodes && *mynodes) {
of_node_set_flag(*mynodes, OF_DETACHED);
pr_debug("unflattened tree is detached\n");
}
pr_debug(" <- unflatten_device_tree()\n");
return mem;
}
依据pr_debug以及注释可以看到这个函数的流程很简单,核心部分在于两次unflatten_dt_nodes的调用。第一次调用计算出了需要分配的内存大小(使用局部变量base=mem,第一次调用增加mem,之后返回mem - base),分配完内存后,第二次调用填充数据(此时参数带了申请的mem)。
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
struct device_node *nps[FDT_MAX_DEPTH];
// 如果mem存在,则dryrun为false
// 若mem不存在,则dryrun为true
void *base = mem;
bool dryrun = !base;
int ret;
if (nodepp)
*nodepp = NULL;
/*
* We're unflattening device sub-tree if @dad is valid. There are
* possibly multiple nodes in the first level of depth. We need
* set @depth to 1 to make fdt_next_node() happy as it bails
* immediately when negative @depth is found. Otherwise, the device
* nodes except the first one won't be unflattened successfully.
*/
if (dad)
depth = initial_depth = 1;
root = dad;
nps[depth] = dad;
// 遍历所有的node
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;
if (!IS_ENABLED(CONFIG_OF_KOBJ) &&
!of_fdt_device_is_available(blob, offset))
continue;
// 核心函数populate_node
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];
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;
}
在unflatten_dt_nodes函数中,遍历所有的node,在node的遍历过程中,调用populate_node函数。
static int populate_node(const void *blob,
int offset,
void **mem,
struct device_node *dad,
struct device_node **pnp,
bool dryrun)
{
struct device_node *np;
const char *pathp;
int len;
pathp = fdt_get_name(blob, offset, &len);
if (!pathp) {
*pnp = NULL;
return len;
}
len++;
np = unflatten_dt_alloc(mem, sizeof(struct device_node) + len,
__alignof__(struct device_node));
if (!dryrun) {
char *fn;
of_node_init(np);
np->full_name = fn = ((char *)np) + sizeof(*np);
memcpy(fn, pathp, len);
if (dad != NULL) {
np->parent = dad;
np->sibling = dad->child;
dad->child = np;
}
}
populate_properties(blob, offset, mem, np, pathp, dryrun);
if (!dryrun) {
np->name = of_get_property(np, "name", NULL);
if (!np->name)
np->name = "<NULL>";
}
*pnp = np;
return 0;
}
在使用unflatten_dt_alloc函数分配完device_node的内存之后,调用populate_properties函数分配device_node管理的所有property。
static void populate_properties(const void *blob,
int offset,
void **mem,
struct device_node *np,
const char *nodename,
bool dryrun)
{
struct property *pp, **pprev = NULL;
int cur;
bool has_name = false;
pprev = &np->properties;
for (cur = fdt_first_property_offset(blob, offset);
cur >= 0;
cur = fdt_next_property_offset(blob, cur)) {
const __be32 *val;
const char *pname;
u32 sz;
val = fdt_getprop_by_offset(blob, cur, &pname, &sz);
if (!val) {
pr_warn("Cannot locate property at 0x%x\n", cur);
continue;
}
if (!pname) {
pr_warn("Cannot find property name at 0x%x\n", cur);
continue;
}
if (!strcmp(pname, "name"))
has_name = true;
pp = unflatten_dt_alloc(mem, sizeof(struct property),
__alignof__(struct property));
if (dryrun)
continue;
/* 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") ||
!strcmp(pname, "linux,phandle")) {
if (!np->phandle)
np->phandle = be32_to_cpup(val);
}
/* And we process the "ibm,phandle" property
* used in pSeries dynamic device tree
* stuff
*/
if (!strcmp(pname, "ibm,phandle"))
np->phandle = be32_to_cpup(val);
pp->name = (char *)pname;
pp->length = sz;
pp->value = (__be32 *)val;
*pprev = pp;
pprev = &pp->next;
}
/* With version 0x10 we may not have the name property,
* recreate it here from the unit name if absent
*/
if (!has_name) {
const char *p = nodename, *ps = p, *pa = NULL;
int len;
while (*p) {
if ((*p) == '@')
pa = p;
else if ((*p) == '/')
ps = p + 1;
p++;
}
if (pa < ps)
pa = p;
len = (pa - ps) + 1;
pp = unflatten_dt_alloc(mem, sizeof(struct property) + len,
__alignof__(struct property));
if (!dryrun) {
pp->name = "name";
pp->length = len;
pp->value = pp + 1;
*pprev = pp;
memcpy(pp->value, ps, len - 1);
((char *)pp->value)[len - 1] = 0;
pr_debug("fixed up name for %s -> %s\n",
nodename, (char *)pp->value);
}
}
}
这里主要是使用fdt_first_property_offset函数和fdt_next_property_offset函数对node的所有properties进行遍历并进行内容填充,注意如果没有name这个properties,则会手动创建。
标签:node,kernel,struct,dtb,tree,offset,device,property,name From: https://www.cnblogs.com/codetrap/p/17747472.html