由于工作需求,不得不看内核源码。有关于 dts 的相关解析知识跟大家分享下,这块也是博主看了好长时间的源码才串起来了。做个博客记录下,顺便复习一遍,也对有需要的人是个参考。
关于dts我就不详解介绍了,网上博客一大堆。我们知道在内核启动的过程中,由于dts的加入,我们先是将dts解析成树型结构,然后放在内存中,等待内核后面注册devices和driver的时候再来铺开调用。
那么dts是在哪块被解析的呢?又是怎么进行解析的呢。内核的启动是在 start_kernel() 函数中进行启动的,在 start_kernel() 函数中调用了 setup_arch(&command_line) 架构相关的函数。因为我们分析的是64位的系统,所以 setup_arch() 在 arch/arm64/kernel/setup.c 中。我们进入到这个函数中,涉及到dts的有三个函数,分别是 setup_machine_fdt(__fdt_pointer); arm64_memblock_init(); unflatten_device_tree(); 接下来我们就分别分析下这三个函数,在他们里面是怎样进行dts的创建及解析的。
A、首先是 setup_machine_fdt(),我们进到函数里,它位于 arch/arm64/kernel/setup.c 中。函数源码如下
static void __init setup_machine_fdt(phys_addr_t dt_phys)
{
void *dt_virt = fixmap_remap_fdt(dt_phys);
if (!dt_virt || !early_init_dt_scan(dt_virt)) {
pr_crit("\n"
"Error: invalid device tree blob at physical address %pa (virtual address 0x%p)\n"
"The dtb must be 8-byte aligned and must not exceed 2 MB in size\n"
"\nPlease check your bootloader.",
&dt_phys, dt_virt);
while (true)
cpu_relax();
}
dump_stack_set_arch_desc("%s (DT)", of_flat_dt_get_machine_name());
}
在这个函数中,我们再追进去看看 fixmap_remap_fdt() 和 early_init_dt_scan()。
fixmap_remap_fdt() 源码如下
void *__init fixmap_remap_fdt(phys_addr_t dt_phys)
{
void *dt_virt;
int size;
dt_virt = __fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL_RO);
if (!dt_virt)
return NULL;
memblock_reserve(dt_phys, size);
return dt_virt;
}
我们可以看出这个函数是为 fdt 建立地址映射,在该函数的最后,顺便就调用 memblock_reserve 保留了该段内存
early_init_dt_scan() 源码如下
bool __init early_init_dt_scan(void *params)
{
bool status;
status = early_init_dt_verify(params);
if (!status)
return false;
early_init_dt_scan_nodes();
return true;
}
我们可以看出在这个函数里又调用了 early_init_dt_verify(params) 和 early_init_dt_scan_nodes() 函数,再追进去看看这两个函数分别实现了什么
early_init_dt_verify(params) 源码如下
bool __init early_init_dt_verify(void *params)
{
if (!params)
return false;
/* check device tree validity */
if (fdt_check_header(params))
return false;
/* Setup flat device-tree pointer */
initial_boot_params = params;
of_fdt_crc32 = crc32_be(~0, initial_boot_params,
fdt_totalsize(initial_boot_params));
return true;
}
通过源码,我们可以不难看出这个函数主要是检验头部,判断设备树有效性并且设置设备树的指针。
early_init_dt_scan_nodes() 源码如下
void __init early_init_dt_scan_nodes(void)
{
/* Retrieve various information from the /chosen node */
of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
/* Initialize {size,address}-cells info */
of_scan_flat_dt(early_init_dt_scan_root, NULL);
/* Setup memory, calling early_init_dt_add_memory_arch */
of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}
通过源码,我们可以看出这个函数做了三个事:a> 从设备树中读取chosen节点的信息,包括命令行boot_command_line,initrd location及size;b> 得到根节点的{size,address}-cells信息;c> 读出设备树的系统内存设置。
那么通过源码的阅读,我们可以看出这个 setup_machine_fdt() 函数主要是为了输入设备树(DTB)首地址,以便在后面进行调用。
B、下面是 arm64_memblock_init() 这个函数,它位于 arch/arm64/mm/init.c 中。函数源码如下
void __init arm64_memblock_init(void)
{
const s64 linear_region_size = -(s64)PAGE_OFFSET;
BUILD_BUG_ON(linear_region_size != BIT(VA_BITS - 1));
memstart_addr = round_down(memblock_start_of_DRAM(),
ARM64_MEMSTART_ALIGN);
memblock_remove(max_t(u64, memstart_addr + linear_region_size,
__pa_symbol(_end)), ULLONG_MAX);
if (memstart_addr + linear_region_size < memblock_end_of_DRAM()) {
/* ensure that memstart_addr remains sufficiently aligned */
memstart_addr = round_up(memblock_end_of_DRAM() - linear_region_size,
ARM64_MEMSTART_ALIGN);
memblock_remove(0, memstart_addr);
}
if (memory_limit != (phys_addr_t)ULLONG_MAX) {
memblock_mem_limit_remove_map(memory_limit);
memblock_add(__pa_symbol(_text), (u64)(_end - _text));
}
memblock_reserve(__pa_symbol(_text), _end - _text);
early_init_fdt_scan_reserved_mem();
/* 4GB maximum for 32-bit only capable devices */
if (IS_ENABLED(CONFIG_ZONE_DMA))
arm64_dma_phys_limit = max_zone_dma_phys();
else
arm64_dma_phys_limit = PHYS_MASK + 1;
high_memory = __va(memblock_end_of_DRAM() - 1) + 1;
dma_contiguous_reserve(arm64_dma_phys_limit);
memblock_allow_resize();
}
我们可以看到在里面调用了 early_init_fdt_scan_reserved_mem() 这个函数。我们再追进去看看里面做了什么事,源码如下
void __init early_init_fdt_scan_reserved_mem(void)
{
int n;
u64 base, size;
if (!initial_boot_params)
return;
/* Process header /memreserve/ fields */
for (n = 0; ; n++) {
fdt_get_mem_rsv(initial_boot_params, n, &base, &size);
if (!size)
break;
early_init_dt_reserve_memory_arch(base, size, 0);
}
of_scan_flat_dt(__fdt_scan_reserved_mem, NULL);
fdt_init_reserved_mem();
}
在里面分别调用了 __fdt_scan_reserved_mem() 和 fdt_init_reserved_mem()。
__fdt_scan_reserved_mem() 源码如下
/**
* fdt_scan_reserved_mem() - scan a single FDT node for reserved memory
*/
static int __init __fdt_scan_reserved_mem(unsigned long node, const char *uname,
int depth, void *data)
{
static int found;
const char *status;
int err;
if (!found && depth == 1 && strcmp(uname, "reserved-memory") == 0) {
if (__reserved_mem_check_root(node) != 0) {
pr_err("Reserved memory: unsupported node format, ignoring\n");
/* break scan */
return 1;
}
found = 1;
/* scan next node */
return 0;
} else if (!found) {
/* scan next node */
return 0;
} else if (found && depth < 2) {
/* scanning of /reserved-memory has been finished */
return 1;
}
status = of_get_flat_dt_prop(node, "status", NULL);
if (status && strcmp(status, "okay") != 0 && strcmp(status, "ok") != 0)
return 0;
err = __reserved_mem_reserve_reg(node, uname);
if (err == -ENOENT && of_get_flat_dt_prop(node, "size", NULL))
fdt_reserved_mem_save_node(node, uname, 0, 0);
/* scan next node */
return 0;
}
我们可以看到这个函数是用来解析reserved-memory节点的内存的,status = of_get_flat_dt_prop(node, "status", NULL) 这句是用来获取设备树属性status,如果是okay则向下,err = __reserved_mem_reserve_reg(node, uname) 这个函数则是用来 判断reg属性,如果没有此节点则保留该节点然后再继续继续扫描下一个节点节点。
总得来说,a> early_init_fdt_scan_reserved_mem() 这个函数是用来分析dts中的节点,从而进行保留内存的动作;b>fdt_init_reserved_mem() 函数则是用来预留reserved-memory节点的内存的。
C、最后是 unflatten_device_tree() 函数,它位于 drivers/of/fdt.c 中,函数源码如下
void __init unflatten_device_tree(void)
{
__unflatten_device_tree(initial_boot_params, NULL, &of_root,
early_init_dt_alloc_memory_arch, false);
/* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */
of_alias_scan(early_init_dt_alloc_memory_arch);
}
在这个函数中调用了 __unflatten_device_tree() 和 of_alias_scan(),追进去看看分别做了什么事
static void *__unflatten_device_tree(const void *blob,
struct device_node *dad,
struct device_node **mynodes,
void *(*dt_alloc)(u64 size, u64 align),
bool detached)
{
int size;
void *mem;
pr_debug(" -> unflatten_device_tree()\n");
if (!blob) {
pr_debug("No device tree pointer\n");
return NULL;
}
pr_debug("Unflattening device tree:\n");
pr_debug("magic: %08x\n", fdt_magic(blob));
pr_debug("size: %08x\n", fdt_totalsize(blob));
pr_debug("version: %08x\n", fdt_version(blob));
if (fdt_check_header(blob)) {
pr_err("Invalid device tree blob header\n");
return NULL;
}
/* First pass, scan for size */
size = unflatten_dt_nodes(blob, NULL, dad, NULL);
if (size < 0)
return NULL;
size = ALIGN(size, 4);
pr_debug(" size is %d, allocating...\n", size);
/* Allocate memory for the expanded device tree */
mem = dt_alloc(size + 4, __alignof__(struct device_node));
if (!mem)
return NULL;
memset(mem, 0, size);
*(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);
pr_debug(" unflattening %p...\n", mem);
/* Second pass, do actual unflattening */
unflatten_dt_nodes(blob, mem, dad, mynodes);
if (be32_to_cpup(mem + size) != 0xdeadbeef)
pr_warning("End of tree marker overwritten: %08x\n",
be32_to_cpup(mem + size));
if (detached && mynodes) {
of_node_set_flag(*mynodes, OF_DETACHED);
pr_debug("unflattened tree is detached\n");
}
pr_debug(" <- unflatten_device_tree()\n");
return mem;
}
在这个函数调用了两次 unflatten_dt_nodes() 函数,第一次scan是为了得到保存所有node和property所需要的内存size;第二次调用才是具体填充每一个struct device_node和struct property结构体。
总得来说,a> unflatten_device_tree() 函数是为了解析dtb文件,DTS节点信息被解析出来,将DTB转换成节点是device_node的树状结构;b> of_alias_scan() 是在设置内核输出终端,以及遍历“/aliases”节点下的所有的属性并挂入相应链表。
那么在执行完以上三个函数之后,dts将会在被展开成树状结构分布在内存中,等待后续的devices 和 driver 注册的时候再来调用。以下是他们的关系图
setup_arch()
|
|------> setup_machine_fdt(__fdt_pointer)
| 输入设备树(DTB)首地址
|
|------> fixmap_remap_fdt()
| 为fdt建立地址映射,在该函数的最后,顺便就调用memblock_reserve 保留了该段内存
|
|------> early_init_dt_scan()
|------> early_init_dt_verify()
| 检验头部,判断设备树有效性并且设置设备树的指针
|
|------> early_init_dt_scan_nodes()
|
|---->of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line)
| 从设备树中读取chosen节点的信息,包括命令行 boot_command_line,initrd location及size
|
|----> of_scan_flat_dt(early_init_dt_scan_root, NULL)
| 得到根节点的{size,address}-cells信息
|
|----> of_scan_flat_dt(early_init_dt_scan_memory, NULL)
| 读出设备树的系统内存设置
|
|--------> arm64_memblock_init()
|
|------> early_init_fdt_scan_reserved_mem()
| 分析dts中的节点,从而进行保留内存的动作
|
|------> __fdt_scan_reserved_mem()
| 解析reserved-memory节点的内存
|
|----> status = of_get_flat_dt_prop(node, "status", NULL)
| 获取设备树属性status,如果是okay则向下
|
|----> err = __reserved_mem_reserve_reg(node, uname)
| 判断reg属性,如果没有此节点则保留该节点;
| 继续扫描下一个节点节点
|
|------> fdt_init_reserved_mem()
| 预留reserved-memory节点的内存
|
|--------> unflatten_device_tree()
| 解析dtb文件,将DTB转换成节点是device_node的树状结构
|
|----> __unflatten_device_tree()
| 第一次scan是为了得到保存所有node和property所需要的内存size;
| 第二次调用才是具体填充每一个struct device_node和struct property结构体
|
|----> of_alias_scan()
| 设置内核输出终端,以及遍历“/aliases”节点下的所有的属性并挂入相应链表
|
|
| 在执行完unflatten_device_tree()后,DTS节点信息被解析出来,保存到allnodes链表中,allnodes在后面会被用到
|