本文从设备树软件控制相关代码进行分析,进而理清设备树相关的知识。
先放一个设备树在内存中的结构图:
分析来源为$(tree)/lib/fdtdec_test.c
每个dtb都包含如下结构的文件头,用来表示设备树的基础信息。
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[0];
};
static int make_fdt(void *fdt, int size, const char *aliases,
const char *nodes)
{
char name[20], value[20];
const char *s;
int fd;
// 根据size创建fdt对象,此处size=16KB
CHECK(fdt_create(fdt, size));
// 设置保留内存段
CHECK(fdt_finish_reservemap(fdt));
// 添加开始节点
CHECK(fdt_begin_node(fdt, ""));
CHECK(fdt_begin_node(fdt, "aliases"));
for (s = aliases; *s;) {
sprintf(name, "i2c%c", *s);
sprintf(value, "/i2c%d@0", s[1] - 'a');
CHECK(fdt_property_string(fdt, name, value));
s += 2 + (s[2] != '\0');
}
CHECK(fdt_end_node(fdt));
for (s = nodes; *s; s++) {
sprintf(value, "i2c%d@0", (*s & 0xdf) - 'A');
CHECK(fdt_begin_node(fdt, value));
CHECK(fdt_property_string(fdt, "compatible",
fdtdec_get_compatible(COMPAT_UNKNOWN)));
if (*s <= 'Z')
CHECK(fdt_property_string(fdt, "status", "disabled"));
CHECK(fdt_end_node(fdt));
}
CHECK(fdt_end_node(fdt));
CHECK(fdt_finish(fdt));
CHECK(fdt_pack(fdt));
#if defined(DEBUG) && defined(CONFIG_SANDBOX)
fd = os_open("/tmp/fdtdec-text.dtb", OS_O_CREAT | OS_O_WRONLY);
if (fd == -1) {
printf("Could not open .dtb file to write\n");
return -1;
}
os_write(fd, fdt, size);
os_close(fd);
#endif
return 0;
}
/* 根据空余空间分配新的节点,新分配的node位于dt_struct下面 */
int fdt_begin_node(void *fdt, const char *name)
{
struct fdt_node_header *nh;
int namelen = strlen(name) + 1;
FDT_SW_CHECK_HEADER(fdt);
// 分配存放node空间
nh = _fdt_grab_space(fdt, sizeof(*nh) + FDT_TAGALIGN(namelen));
if (! nh)
return -FDT_ERR_NOSPACE;
// 设置节点tag,name
nh->tag = cpu_to_fdt32(FDT_BEGIN_NODE);
memcpy(nh->name, name, namelen);
return 0;
}
/* include/libfdt.h */
#define fdt_property_string(fdt, name, str) \
fdt_property(fdt, name, str, strlen(str)+1)
/* lib/libfdt/fdt_sw.c */
int fdt_property(void *fdt, const char *name, const void *val, int len)
{
struct fdt_property *prop;
int nameoff;
FDT_SW_CHECK_HEADER(fdt);
/* 在字符串块中搜索有没有名字为name的字符串,返回存放的字符串的地址
* 这个地址是从字符串块的底部开始计算偏移的。
*/
nameoff = _fdt_find_add_string(fdt, name);
if (nameoff == 0)
return -FDT_ERR_NOSPACE;
prop = _fdt_grab_space(fdt, sizeof(*prop) + FDT_TAGALIGN(len));
if (! prop)
return -FDT_ERR_NOSPACE;
/* 属性节点存放了tag,value以及name的偏移和长度 */
prop->tag = cpu_to_fdt32(FDT_PROP);
prop->nameoff = cpu_to_fdt32(nameoff);
prop->len = cpu_to_fdt32(len);
memcpy(prop->data, val, len);
return 0;
}
注意: 属性name和value的格式
aliases = "1e 3d"
for (s = aliases; *s;) {
/* 名字是字符串加数字 */
sprintf(name, "i2c%c", *s);
/* value是路径@地址 */
sprintf(value, "/i2c%d@0", s[1] - 'a');
CHECK(fdt_property_string(fdt, name, value));
s += 2 + (s[2] != '\0');
}
int fdt_end_node(void *fdt);
执行这个意味着该节点到此结束了。其实就是在node末尾写了一个标识符FDT_END_NODE
, 说明该节点结束了,下面开始新的节点定义。
每一个节点的定义都经历了2.1节这样的过程。
int fdt_finish(void *fdt)
这个函数首先在未分配空间顶部写入FDT_END
,然后通过把字符串块移动到结构块下面,消除剩余的未分配空间,再重新计算字符串块中的各个name的偏移,最后调整文件头,因为totalsize变了。
这个时候还要计算reserved mem大小,reserved mem位于header和dt_struct之间。然后重新计算结构块和字符串块的偏移。基本上就是重复2.2的过程。打包完成后就生成了最终的dtb文件。