arm64 - 设备树的加载流程

设备树的加载流程

dts文件经过dtc工具编译为dtb,内核加载并解析dtb文件,最终获得设备树的信息。

1. 设备树地址设置

我们一般通过Bootloader引导启动Kernel,在启动Kernel之前,Bootloader必须将dtb文件的首地址传输给Kernel,以供使用。

  1. Bootloader将dtb二进制文件的起始地址写入x0寄存器中
  2. Kernel在第一个启动文件head.S中,读取x0寄存器中的值,将fdt首地址暂时存放至x21,释放x0用于其他使用
/*
 * Preserve the arguments passed by the bootloader in x0 .. x3
 */
SYM_CODE_START_LOCAL(preserve_boot_args)
    mov x21, x0             // x21=FDT

    adr_l   x0, boot_args           // record the contents of
    stp x21, x1, [x0]           // x0 .. x3 at kernel entry
    stp x2, x3, [x0, #16]

    dmb sy              // needed before dc ivac with
                        // MMU off

    mov x1, #0x20           // 4 x 8 bytes
    b   __inval_dcache_area     // tail call
SYM_CODE_END(preserve_boot_args)

  1. x21(fdt首地址)物理地址写入__fdt_pointer变量中,x5为一个临时变量用于暂存4k页中__fdt_pointer的偏移地址
/*
 * The following fragment of code is executed with the MMU enabled.
 *
 *   x0 = __PHYS_OFFSET
 */
SYM_FUNC_START_LOCAL(__primary_switched)
    adrp    x4, init_thread_union
    add sp, x4, #THREAD_SIZE
    adr_l   x5, init_task
    msr sp_el0, x5          // Save thread_info
.
.
    str_l   x21, __fdt_pointer, x5      // Save FDT pointer
.
.
    b   start_kernel
SYM_FUNC_END(__primary_switched)
  1. 跳转入口函数start_kernel执行C语言代码

2. 和device tree相关的setup_arch代码分析

static void __init setup_machine_fdt(phys_addr_t dt_phys)
{
    int size;
    void *dt_virt = fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL);----(1)
    const char *name;

    if (dt_virt)
        memblock_reserve(dt_phys, size);

    if (!dt_virt || !early_init_dt_scan(dt_virt)) {  --------(2)
        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();
    }

    /* Early fixups are done, map the FDT as read-only now */
    fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL_RO);

    name = of_flat_dt_get_machine_name();
    if (!name)
        return;

    pr_info("Machine model: %s\n", name);
    dump_stack_set_arch_desc("%s (DT)", name);
}

(1)fixmap_remap_fdt:

将物理地址 dt_phys 映射到虚拟地址 dt_virt , 并且 得到 fdt 的长度 size .如果映射成功,即 dt_virt不为 0 ,使用memblock 模块的 reserve 函数,将 dt_phys 开始 size 大小的这块内存,在 memblock 模块中预留下来

(2)early_init_dt_scan:

early_init_dt_scan —> early_init_dt_verify

设置设备树指针 initial_boot_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 —> early_init_dt_scan_nodes

early_init_dt_scan_nodes函数中通过of_scan_flat_dt函数扫描整个设备树,实际动作是在回调函数中完成的

void __init early_init_dt_scan_nodes(void)
{
    int rc = 0;

    /* Retrieve various information from the /chosen node */
    rc = of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line); ----(a)
    if (!rc)
        pr_warn("No chosen node found, continuing without\n");

    /* Initialize {size,address}-cells info */
    of_scan_flat_dt(early_init_dt_scan_root, NULL);   ----(b)

    /* Setup memory, calling early_init_dt_add_memory_arch */
    of_scan_flat_dt(early_init_dt_scan_memory, NULL);   ----(c)
}

a. 解析设备树文件,提取chosen节点中bootargs保存到 boot_command_line 中

一般而言,内核有可能会定义一个default command line string(CONFIG_CMDLINE),如果bootloader没有通过device tree传递命令行参数过来,那么可以考虑使用default参数。如果系统定义了CONFIG_CMDLINE_FORCE,那么系统强制使用缺省命令行参数,bootloader传递过来的是无效的。

b. 根据节点的#address-cells属性和#size-cells属性初始化全局变量dt_root_size_size_cells和dt_root_addr_cells,如果没有设置属性的话这里就使用默认值

c. 内存的初始化。得到每块内存的起始地址和大小后,再调用early_init_dt_add_memory_arch函数。

int __init early_init_dt_scan_memory(unsigned long node, const char *uname,int depth, void *data)
{
    ...
    base = dt_mem_next_cell(dt_root_addr_cells, ®);
    size = dt_mem_next_cell(dt_root_size_cells, ®);
    early_init_dt_add_memory_arch(base, size);
}

3. 初始化流程

/**
 * unflatten_device_tree - create tree of device_nodes from flat blob
 *
 * unflattens the device-tree passed by the firmware, creating the
 * tree of struct device_node. It also fills the "name" and "type"
 * pointers of the nodes so the normal device-tree walking functions
 * can be used.
 */
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);

	unittest_unflatten_overlay_base();
}

我们用struct device_node 来抽象设备树中的一个节点,具体解释如下:

struct device_node {
	const char *name;                  ---device node name
	phandle phandle;                   ---对应该节点的phandle属性
	const char *full_name;             ---从“/”开始的,表示该node的full path
	struct fwnode_handle fwnode;

	struct	property *properties;      ---该节点的属性列表
	struct	property *deadprops;	   ---如果需要删除某些属性,kernel并非真的删除,而是挂入到deadprops的列表
	struct	device_node *parent;       ---parent、child以及sibling将所有的device node连接起来
	struct	device_node *child;
	struct	device_node *sibling;
#if defined(CONFIG_OF_KOBJ)
	struct	kobject kobj;
#endif
	unsigned long _flags;
	void	*data;
#if defined(CONFIG_SPARC)
	unsigned int unique_id;
	struct of_irq_controller *irq_trans;
#endif
};

unflatten_device_tree函数的主要功能就是扫描DTB,将device node组织成:

  1. global list。全局变量struct device_node *of_allnodes就是指向设备树的global list
  2. tree。

这些功能主要是在__unflatten_device_tree函数中实现:

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)) {                    ------(a)
		pr_err("Invalid device tree blob header\n");
		return NULL;
	}

	/* First pass, scan for size */
	size = unflatten_dt_nodes(blob, NULL, dad, NULL);  ------(b)
	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));  ------(c)
	if (!mem)
		return NULL;

	memset(mem, 0, size);

	*(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);   ------(d)

	pr_debug("  unflattening %p...\n", mem);

	/* Second pass, do actual unflattening */
	unflatten_dt_nodes(blob, mem, dad, mynodes);     ------(e)
	if (be32_to_cpup(mem + size) != 0xdeadbeef)
		pr_warn("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;
}

(a) 检查DTB header的magic,确认blob的确指向一个DTB

(b) scan过程分成两轮,第一轮主要是确定device-tree structure的长度,保存在size变量中

© 初始化的时候,并不是扫描到一个node或者property就分配相应的内存,实际上内核是一次性的分配了一大片内存,这些内存包括了所有的struct device_node、node name、struct property所需要的内存

(d) 用来检验后面unflattening是否溢出

(e) 这是第二轮的scan,第一次scan是为了得到保存所有node和property所需要的内存size,第二次就是实打实的要构建device node tree了

你可能感兴趣的:(linux,kernel,c++,java,开发语言)