设备树编译器DTC的总体流程

1、程序入口
在dtc源码文件的dtc.c文件中的main()函数中
2、流程
首先确定命令行参数的正确性和合理性,使用util_getopt_long()函数解析命令的输入参数。命令行参数如下:
-I 选项 确认输入文件格式
-O 选项 确认输出文件格式
-o 选项 确认输出文件的名字
-V 选项 打印输出版本
-d 选项 确认依赖的文件
-R 选项 确认内存保留映射项的数量
-S 选项 指定FDT的最小空间大小
-P 选项 确定FDT中的填充大小
-a 选项 对FDT文件的对齐要求
-f 选项 强制选项
-q 选项 指定告警,错误或全部支持(单个q支持编译告警,两个q支持编译错误,三个q告警和错误都支持)
-b 选项 指定从哪个CPU核启动
-i 选项 添加搜索路径
-v 选项 版本显示
-H 选项 设备树中phandle的支持
-s 选项 在输出DTB前排序节点和属性
-W 选项 解析检查选项,支持告警
-E 选项 支持错误
-@ 选项 支持产生__symbols__节点
-A 选项 支持产生aliases节点
-T 选项 支持注释输出
-h 选项 打印这个命令的帮助信息
下面是对上面解析参数的进一步判断,确认好后读取输入dts格式,fs格式或dtb格式的文件,进行文件解析,函数是dti = dt_from_source(arg);
2.1 上面的解析dts文件的源文件在treesource.c文件中,这个函数调用flex的词法解析器和bison语法解析器,函数内容如下:

         struct dt_info *dt_from_source(const char *fname)
          {
          	  parser_output = NULL;
            	treesource_error = false;

            	srcfile_push(fname);
            	yyin = current_srcfile->f;
            	yylloc.file = current_srcfile;

            	if (yyparse() != 0)                          //词法解析器入口
            		die("Unable to parse input tree\n");

            	if (treesource_error)
            		die("Syntax error parsing input tree\n");

            	return parser_output;                      //解析完成后输出总的解析后的数据
          } 

2.2 填充节点node数据结构中的char *fullpath,调用函数fill_fullpaths(dti->dt, "");完成,填充每个节点相对于根节点的路径
2.3 对解析完成后的dti数据结构进行检查,调用process_checks(force, dti);函数,这个函数根据命令行的设置对解析完成的数据结构进行语义检查
2.4 根据是否产生aliases,__sysbols__和__fixups__和__local_fixups__产生对应的节点
2.5 根据命令行参数决定设备树结构是否排序sort_tree(dti);
2.6 根据输出文件的格式,调用相应的产生输出文件的函数,这里dtb调用dt_to_blob(outf, dti, outversion)产生最终的dtb文件

3、dt_to_blob(outf, dti, outversion)函数按照设备树标准文件中第5章节平坦设备树(FDT)格式产生对应格式的文件。
FDT文件的总体格式如下:

FDT头的格式如下:

下面是实际解析RK3308的一个dtb文件的头部信息

接下来是保留内存块的信息,格式如下:

下面是实际解析RK3308的一个dtb文件的内存保留信息

接下来是结构块的信息,结构块主要由下面的信息组成:
1、(可选的)多个FDT_NOP标记
2、FDT_BEGIN_NODE标记:
- 节点名,它是以NULL结尾的字符串
- [4字节对齐的0填充字节]
3、节点的多个属性:
- (可选的)多个FDT_NOP标记
- FDT_DROP属性节点标记
* 属性信息
* [4字节对齐的多个0填充字节]
4、 上面格式重复的所有子节点
5、(可选的)多个FDT_NOP标记
6、FDT_END_NODE标记
下面是实际解析RK3308的一个dtb文件的结构块信息:


接下来是字符串块的信息,字符串之间以‘\0’表示一个字符串的结束
实际的解析如下图所示:

创建结构块和字符串块的函数是flatten_tree(dti->dt, &bin_emitter, &dtbuf, &strbuf, vi),其中第二个参数&bin_emitter是对节点和属性的操作函数,其信息如下所示:

  static struct emitter bin_emitter = {
    .cell = bin_emit_cell,
	.string = bin_emit_string,
	.align = bin_emit_align,
	.data = bin_emit_data,
	.beginnode = bin_emit_beginnode,
	.endnode = bin_emit_endnode,
	.property = bin_emit_property,
  };

开始节点FDT_BEGIN_NODE如下:

static void bin_emit_beginnode(void *e, struct label *labels)
{
	bin_emit_cell(e, FDT_BEGIN_NODE);
}

static void bin_emit_cell(void *e, cell_t val)
{
	struct data *dtbuf = e;

	*dtbuf = data_append_cell(*dtbuf, val);
}

添加属性FDT_PROP如下:

static void bin_emit_property(void *e, struct label *labels)
{
	bin_emit_cell(e, FDT_PROP);
}

添加结束FDT_END_NODE如下:

static void bin_emit_endnode(void *e, struct label *labels)
{
	bin_emit_cell(e, FDT_END_NODE);
}

字节对齐操作如下:

static void bin_emit_align(void *e, int a)
{
	struct data *dtbuf = e;

	*dtbuf = data_append_align(*dtbuf, a);
}

添加数据操作如下:

static void bin_emit_data(void *e, struct data d)
{
	struct data *dtbuf = e;

	*dtbuf = data_append_data(*dtbuf, d.val, d.len);
}

添加字符串操作如下:

static void bin_emit_string(void *e, const char *str, int len)
{
	struct data *dtbuf = e;

	if (len == 0)
		len = strlen(str);

	*dtbuf = data_append_data(*dtbuf, str, len);
	*dtbuf = data_append_byte(*dtbuf, '\0');
}

内存保留信息数据结构缓冲区操作如下:

reservebuf = flatten_reserve_list(dti->reservelist, vi);

创建FDT头数据结构如下:

make_fdt_header(&fdt, vi, reservebuf.len, dtbuf.len, strbuf.len,dti->boot_cpuid_phys);

static void make_fdt_header(struct fdt_header *fdt,
		    struct version_info *vi,
		    int reservesize, int dtsize, int strsize,
		    int boot_cpuid_phys)
{
	int reserve_off;

	reservesize += sizeof(struct fdt_reserve_entry);

	memset(fdt, 0xff, sizeof(*fdt));

	fdt->magic = cpu_to_fdt32(FDT_MAGIC);
	fdt->version = cpu_to_fdt32(vi->version);
	fdt->last_comp_version = cpu_to_fdt32(vi->last_comp_version);

	/* Reserve map should be doubleword aligned */
	reserve_off = ALIGN(vi->hdr_size, 8);

	fdt->off_mem_rsvmap = cpu_to_fdt32(reserve_off);
	fdt->off_dt_struct = cpu_to_fdt32(reserve_off + reservesize);
	fdt->off_dt_strings = cpu_to_fdt32(reserve_off + reservesize + dtsize);
	fdt->totalsize = cpu_to_fdt32(reserve_off + reservesize + dtsize + strsize);

	if (vi->flags & FTF_BOOTCPUID)
		fdt->boot_cpuid_phys = cpu_to_fdt32(boot_cpuid_phys);
	if (vi->flags & FTF_STRTABSIZE)
		fdt->size_dt_strings = cpu_to_fdt32(strsize);
	if (vi->flags & FTF_STRUCTSIZE)
		fdt->size_dt_struct = cpu_to_fdt32(dtsize);
}

在下面的函数中,将FDT头部信息,内存保留信息,数据结构信息和字符串结构信息都添加到最后的数据blob中,如下:

blob = data_append_data(blob, &fdt, vi->hdr_size);
blob = data_append_align(blob, 8);
blob = data_merge(blob, reservebuf);
blob = data_append_zeroes(blob, sizeof(struct fdt_reserve_entry));
blob = data_merge(blob, dtbuf);
blob = data_merge(blob, strbuf);

最后将blob数据结构信息写到输出文件中,如下:

fwrite(blob.val, blob.len, 1, f)

至此设备树文件dts的所有信息被写到输出的dtb文件中。

posted @ 2025-04-07 21:41  xiaobing3314  阅读(102)  评论(0)    收藏  举报