Rickey 裘 一无所知

uboot设备树--生成过程分析

2016-12-27
佚名

本文从设备树软件控制相关代码进行分析,进而理清设备树相关的知识。

先放一个设备树在内存中的结构图:

uboot设备树内存分布

分析来源为$(tree)/lib/fdtdec_test.c

一、数据结构

1.1 文件头

每个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 */
};

1.2 节点头

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;
}

2.1 节点分配

2.1.1 开始分配节点

/* 根据空余空间分配新的节点,新分配的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;
}

2.1.2 属性节点和字符串块

/* 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');
	}

2.1.3 节点结束标记

int fdt_end_node(void *fdt)

执行这个意味着该节点到此结束了。其实就是在node末尾写了一个标识符FDT_END_NODE, 说明该节点结束了,下面开始新的节点定义。

每一个节点的定义都经历了2.1节这样的过程。

2.2 完成设备树

int fdt_finish(void *fdt)

这个函数首先在未分配空间顶部写入FDT_END,然后通过把字符串块移动到结构块下面,消除剩余的未分配空间,再重新计算字符串块中的各个name的偏移,最后调整文件头,因为totalsize变了。

2.3 设备树打包

这个时候还要计算reserved mem大小,reserved mem位于header和dt_struct之间。然后重新计算结构块和字符串块的偏移。基本上就是重复2.2的过程。打包完成后就生成了最终的dtb文件。


评论