dtc工具的使用方法是:dtc –I dts –O dtb –o xxx.dtb xxx.dts,即可生成dts文件对应的dtb文件
Linux终端执行ftddump –h
dtb文件为大端存储模式
struct fdt_header {
uint32_t magic; //dtb文件的固定开始数字 0xd00dfeed
uint32_t totalsize; //dtb文件的大小
uint32_t off_dt_struct; //structure block区域的地址偏移值,从文件开头计算
uint32_t off_dt_strings; //strings block区域的地址偏移值,从文件开头计算
uint32_t off_mem_rsvmap; //memory reservation block区域的地址偏移值,从文件开头计算
uint32_t version; //设备树数据结构的版本
uint32_t last_comp_version; //所用版本向后兼容的最低版本的设备树数据结构
uint32_t boot_cpuid_phys; //系统引导CPU的物理ID,它的值应该与设备树文件中CPU节点下的reg属性值相等
uint32_t size_dt_strings; //structure block区域的字节数
uint32_t size_dt_struct; //strings block区域的字节数
};
struct fdt_reserve_entry {
uint64_t address; //开始地址
uint64_t size; //大小
};
/memreserve/ 0x33f00000 0x100000;
/ {
name = "sample"
model = "SMDK24440";
compatible = "samsung,smdk2440";
#address-cells = <1>;
#size-cells = <1>;
memory { /* /memory */
device_type = "memory";
reg = <0x30000000 0x4000000 0 4096>;
};
/*
cpus {
cpu {
compatible = "arm,arm926ej-s";
};
};
*/
chosen {
bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
};
};
Struct Block是由一系列连续的片组成的,每个片的以一个32位token开始,token按照大字节序存储,某些token后面还会接着额外的数据(extra data)。所有的token都是32bit对齐的,不满32bit的会在前面补0。
struct fdt_node_header {
fdt32_t tag;//对应的token,如FDT_BEGIN_NODE、
char name[0];
};
//描述属性采用struct fdt_property描述,tag标识是属性,取值为FDT_PROP;len为属性值的长度(包括‘\0’,单位:字节);nameoff为属性名称存储位置相对于off_dt_strings的偏移地址
struct fdt_property {
fdt32_t tag;// =FDT_PROP
fdt32_t len;//后接属性值的长度(如果是字符串包括'\0')
fdt32_t nameoff;//属性名在strings block区域的偏移值(很多节点的属性名相同,节省空间)
char data[0];//属性名称起始地址
};
五种类型的token:
led {
compatible = "jz2440_led";
pin = <S3C2410_GPF(5)>;
};
FDT_BEGIN_NODE+节点名称开始,然后是FDT_PROP描述节点的属性,然后是struct(len+nameoff)+val,该节点的属性描述完成后,使用FDT_END_NODE表示节点结束;最后,当所有节点描述完毕后,使用FDT_END结束struct block。
存放struct block中用到的属性名,可能这些属性名重复率很高,这样节省空间。该区域的字符串简单地拼接在一起,没有字节对其。
.init.arch.info : {
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
}
void __init setup_arch(char **cmdline_p)
{
const struct machine_desc *mdesc;
/* 初始化一些处理器相关的全局变量 */
setup_processor();
/* 优先按照设备树获取machine_desc */
mdesc = setup_machine_fdt(__atags_pointer);
/* 如果u-boot传递的不是DTB,则按照ATAGS获取machine_desc */
if (!mdesc)
mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
......
/* 记录获取到的machine_desc及其名字 */
machine_desc = mdesc;
machine_name = mdesc->name;
dump_stack_set_arch_desc("%s", mdesc->name);
......
/* 将boot_command_line的内容拷贝到cmd_line */
strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
/* 输出指向启动参数的指针 */
*cmdline_p = cmd_line;
......
/* 根据DTB创建device_node树 */
unflatten_device_tree();
......
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
/* 设置handle_arch_irq */
handle_arch_irq = mdesc->handle_irq;
#endif
......
/* 调用machine_desc中注册的初始化函数 */
if (mdesc->init_early)
mdesc->init_early();
}
setup_arch函数调用setup_machine_fdt解析设备树中的相关信息,并返回合适的machine_desc
const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
{
const struct machine_desc *mdesc, *mdesc_best = NULL;
/* 验证DTB文件是否存在:地址不为NULL && 文件头部magic正确 */
/* initial_boot_params = dt_phys */
if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys)))
return NULL;
/* 获取compatible属性并匹配合适的machine_desc */
mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
if (!mdesc) {
/* 打印一些信息 */
......
/* 把当前kernel支持的单板的名字和单板ID打印出来 */
/* 该函数不会返回(内部有死循环) */
dump_machine_table();
}
/* 当DTB文件提供的数据有问题,这里会做一些修补工作 */
if (mdesc->dt_fixup)
mdesc->dt_fixup();
/* 获取运行时配置信息,再第3节中细说 */
early_init_dt_scan_nodes();
/* 记录machine ID */
__machine_arch_type = mdesc->nr;
return mdesc;
}
of_flat_dt_match_machine函数是匹配合适的machine_desc的关键
/*
传入的第一个参数为NULL
传入的第二个参数为arch_get_next_mach
arch_get_next_mach的原理非常简单:初始化一个静态局部变量为__arch_info_begin,
每次被调用时该变量(指针)+1并返回,如果超出了__arch_info_end,则返回NULL
*/
const void * __init of_flat_dt_match_machine(const void *default_match,
const void * (*get_next_compat)(const char * const**))
{
const void *data = NULL;
const void *best_data = default_match;
const char *const *compat;
unsigned long dt_root;
unsigned int best_score = ~1, score = 0;
/* dt_root = 0 */
dt_root = of_get_flat_dt_root();
/*
遍历所有machine_desc,将machine_desc的dt_compat保存到compat
compat指向一系列字符串(一个machine_desc也可能支持多个单板)
*/
while ((data = get_next_compat(&compat))) {
/*
DTB根节点的compatible属性值是一系列字符串,假设为"aaa", "bbb", "ccc"
machine_desc的dt_compat(指针的指针)也指向一系列字符串,假设为"xxx", "ccc"
第一轮比较(score = 0):
1、score++, compatible的"aaa"<==>dt_compat的"xxx"
2、score++, compatible的"bbb"<==>dt_compat的"xxx"
3、score++, compatible的"ccc"<==>dt_compat的"xxx"
第二轮比较(score = 0):
1、score++, compatible的"aaa"<==>dt_compat的"ccc"
2、score++, compatible的"bbb"<==>dt_compat的"ccc"
3、score++, compatible的"ccc"<==>dt_compat的"ccc",此时匹配上,返回score(值为3)
*/
score = of_flat_dt_match(dt_root, compat);
/* 记录得分最低(最匹配)的machine_desc */
if (score > 0 && score < best_score) {
best_data = data;
best_score = score;
}
}
/* 没有匹配到合适的machine_desc就返回NULL */
if (!best_data) {
/* 打印根节点的compatible属性值 */
......
return NULL;
}
/* 打印根节点的model属性值,若不存在则打印compatible属性值 */
pr_info("Machine model: %s\n", of_flat_dt_get_machine_name());
return best_data;
}
kernel使用setup_arch ==> setup_machine_fdt ==> early_init_dt_scan_nodes来处理DTB中的运行时配置信息
void __init early_init_dt_scan_nodes(void)
{
int rc = 0;
/* 获取/chosen节点的信息 */
rc = of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
if (!rc)
pr_warn("No chosen node found, continuing without\n");
/* 获取根节点的{size,address}-cells属性值,之后才方便解析根节点的子节点的reg属性 */
of_scan_flat_dt(early_init_dt_scan_root, NULL);
/* 解析/memory节点,设置内存信息 */
of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}
/**
* 遍历DTB的节点,直到参数传入的回调函数it返回非0值
*/
int __init of_scan_flat_dt(int (*it)(unsigned long node,
const char *uname, int depth,
void *data),
void *data)
{
/* blob指向DTB在内存中的起始地址 */
const void *blob = initial_boot_params;
const char *pathp;
int offset, rc = 0, depth = -1;
/* 若设备树不存在则返回 */
if (!blob)
return 0;
/* 从根节点开始遍历 */
for (offset = fdt_next_node(blob, -1, &depth);
/* 如果找到了有效的节点并且回调函数it返回0,则执行循环体 */
offset >= 0 && depth >= 0 && !rc;
/* 继续遍历下一个节点 */
offset = fdt_next_node(blob, offset, &depth)) {
/* 获取节点名 */
pathp = fdt_get_name(blob, offset, NULL);
/* 对于老版本的设备树,得到的是节点的路径名,因此要去掉多余的前缀 */
/* 不过fdt_get_name已经考虑过这个问题了,这里有点多余 */
if (*pathp == '/')
pathp = kbasename(pathp);
/*
调用回调函数it
offset: 节点起始位置在DTB的structure block中的偏移
pathp : 指向节点名
depth : 节点的深度(层次)
data : 参数data,取决于调用者
*/
rc = it(offset, pathp, depth, data);
}
return rc;
}
传入的回调函数early_init_dt_scan_chosen用于解析/chosen节点
/*
offset: 节点起始位置在DTB的structure block中的偏移
pathp : 指向节点名
depth : 节点的深度(层次)
data : boot_command_line,一个字符数组
*/
int __init early_init_dt_scan_chosen(unsigned long node, const char *uname,
int depth, void *data)
{
int l;
const char *p;
const void *rng_seed;
pr_debug("search \"chosen\", depth: %d, uname: %s\n", depth, uname);
/* 如果遍历到的不是作为根节点的子节点的chosen节点,则指示of_scan_flat_dt继续遍历下一个节点 */
if (depth != 1 || !data ||
(strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0))
return 0;
/* 当前节点是/chosen节点 */
/* 解析/chosen节点的initrd属性,设置全局变量phys_initrd_start和phys_initrd_size */
early_init_dt_check_for_initrd(node);
/* 获取/chosen节点的bootargs属性的属性值 */
p = of_get_flat_dt_prop(node, "bootargs", &l);
/* 如果属性存在,则p指向bootargs属性值——一个字符串,l记录了字符串的长度(含'\0') */
if (p != NULL && l > 0)
/* 将启动参数拷贝到boot_command_line */
strlcpy(data, p, min(l, COMMAND_LINE_SIZE));
/*
* CONFIG_CMDLINE配置项意味着如果u-boot传递的参数不含启动参数,那么
* CONFIG_CMDLINE就是默认的启动参数。如果含有启动参数,那么,是追加
* 还是覆盖已有的启动参数,取决于另外两个配置项CONFIG_CMDLINE_EXTEND
* 和CONFIG_CMDLINE_FORCE。
*/
#ifdef CONFIG_CMDLINE
#if defined(CONFIG_CMDLINE_EXTEND)
strlcat(data, " ", COMMAND_LINE_SIZE);
strlcat(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#elif defined(CONFIG_CMDLINE_FORCE)
strlcpy(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#else
/* 如果DTB不带有启动参数,就使用kernel的启动参数——CONFIG_CMDLINE */
if (!((char *)data)[0])
strlcpy(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#endif
#endif /* CONFIG_CMDLINE */
pr_debug("Command line is: %s\n", (char*)data);
/* 对rng-seed节点的解析,暂时不清楚这个东西 */
rng_seed = of_get_flat_dt_prop(node, "rng-seed", &l);
if (rng_seed && l > 0) {
......
}
/* 返回非0值,指示of_scan_flat_dt停止遍历 */
return 1;
}
在解析/memory节点之前,应该先得到根节点的{size,address}-cells属性值,因为/memory节点使用reg属性来存放内存的起始地址和长度,而解析reg属性少不了{size,address}-cells
int __init early_init_dt_scan_root(unsigned long node, const char *uname,
int depth, void *data)
{
const __be32 *prop;
/* 验证当前节点是否是根节点 */
if (depth != 0)
return 0;
/* 设置默认值 */
dt_root_size_cells = OF_ROOT_NODE_SIZE_CELLS_DEFAULT;
dt_root_addr_cells = OF_ROOT_NODE_ADDR_CELLS_DEFAULT;
/* 如果有#size-cells属性则获取其值,并重新设置dt_root_size_cells */
prop = of_get_flat_dt_prop(node, "#size-cells", NULL);
if (prop)
/* 注意大小端的转换 */
dt_root_size_cells = be32_to_cpup(prop);
pr_debug("dt_root_size_cells = %x\n", dt_root_size_cells);
/* 如果存在#address-cells属性,则重新设置dt_root_addr_cells */
prop = of_get_flat_dt_prop(node, "#address-cells", NULL);
if (prop)
dt_root_addr_cells = be32_to_cpup(prop);
pr_debug("dt_root_addr_cells = %x\n", dt_root_addr_cells);
/* 停止遍历 */
return 1;
}
int __init early_init_dt_scan_memory(unsigned long node, const char *uname,
int depth, void *data)
{
/* 获取/memory节点的device_type属性 */
const char *type = of_get_flat_dt_prop(node, "device_type", NULL);
const __be32 *reg, *endp;
int l;
bool hotpluggable;
/* /memory节点的device_type属性值必须是memory */
if (type == NULL || strcmp(type, "memory") != 0)
return 0;
/* 获取/memory节点的linux,usable-memory或reg属性值(存放了内存的起始地址和长度信息) */
reg = of_get_flat_dt_prop(node, "linux,usable-memory", &l);
if (reg == NULL)
reg = of_get_flat_dt_prop(node, "reg", &l);
if (reg == NULL)
return 0;
endp = reg + (l / sizeof(__be32));
/* 获取hotpluggable属性值(指示是否可以热插拔) */
hotpluggable = of_get_flat_dt_prop(node, "hotpluggable", NULL);
pr_debug("memory scan node %s, reg size %d,\n", uname, l);
/* 遍历reg属性记录的一块或多块内存 */
while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {
u64 base, size;
/* 获取当前内存块的起始地址 */
base = dt_mem_next_cell(dt_root_addr_cells, ®);
size = dt_mem_next_cell(dt_root_size_cells, ®);
if (size == 0)
continue;
pr_debug(" - %llx , %llx\n", (unsigned long long)base,
(unsigned long long)size);
/* 对base和size进行一系列校验后,调用memblock_add添加内存块(struct memblock) */
early_init_dt_add_memory_arch(base, size);
if (!hotpluggable)
continue;
/* 若当前内存块可以热插拔,那么标记之 */
if (early_init_dt_mark_hotplug_memory_arch(base, size))
pr_warn("failed to mark hotplug range 0x%llx - 0x%llx\n",
base, base + size);
}
return 0;
}
struct property {
char *name; /* 指向属性名字符串,位于DTB的strings block*/
int length; /* 属性的长度 */
void *value; /* void *类型,指向属性值,位于DTB的structure block */
struct property *next; /* 一个节点的所有属性构成一个链表 */
unsigned long _flags;
unsigned int unique_id;
struct bin_attribute attr; /* 属性文件,与sysfs文件系统挂接 */
};
struct device_node {
const char *name; /* node的名称,取最后一次“/”和“@”之间子串,位于DTB的structure block*/
const char *type; /* device_type的属性名称,没有为 */
phandle phandle; /* phandle属性值 */
const char *full_name; /* 指向该结构体结束的位置,存放node的路径全名,例如:/chosen */
struct fwnode_handle fwnode;
struct property *properties; /* 指向该节点下的第一个属性,其他属性与该属性链表相接 */
struct property *deadprops; /* removed properties */
struct device_node *parent; /* 父节点 */
struct device_node *child; /* 子节点 */
struct device_node *sibling; /* 姊妹节点,与自己同等级的node */
struct kobject kobj; /* sysfs文件系统目录体现 */
unsigned long _flags; /* 当前node状态标志位,见/include/linux/of.h line124-127 */
void *data;
};
/* flag descriptions (need to be visible even when !CONFIG_OF) */
#define OF_DYNAMIC 1 /* node and properties were allocated via kmalloc */
#define OF_DETACHED 2 /* node has been detached from the device tree*/
#define OF_POPULATED 3 /* device already created for the node */
#define OF_POPULATED_BUS 4 /* of_platform_populate recursed to children of this node */
/ {
model = "SMDK2416";
compatible = "samsung,s3c2416";
#address-cells = <1>;
#size-cells = <1>;
memory@30000000 {
device_type = "memory";
reg = <0x30000000 0x4000000>;
};
pinctrl@56000000 {
name = "example_name";
compatible = "samsung,s3c2416-pinctrl";
};
};