设备树编译器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文件中。