【ARM64】DTB地址从uboot传递到kernel的流程

前言

       最近在移植一款蓝牙芯片的过程中用到了设备树,一开始在研究kernel是怎么解析DTB的,后来就很好奇kernel是怎么找到DTB的,所以就有了这篇文章,纯粹记录一下自己的学习过程吧。

正文

       下面我就从两个阶段来讲述,第一个阶段就是uboot是怎么将DTB的地址传递给kernel;第二个阶段就是kernel怎么根据DTB的地址,并解析DTB的。

uboot阶段

        uboot的启动流程我就不在这里具体分析,网上很多的文章,这里只分析在uboot是怎么传递DTB的地址的。在uboot启动的最后阶段,会去执行bootcmd中的命令,其中就是使用下面的命令来启动kernel:

bootm ${loadaddr} //loadaddr是kernel的地址

熟悉uboot的人会知道,这条命令最终会执行到某个具体的函数:

U_BOOT_CMD(
	bootm,	CONFIG_SYS_MAXARGS,	1,	do_bootm,
	"boot application image from memory", bootm_help_text
);

所以最终执行到do_bootm函数

int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
    ...
	nRet = do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START |
		BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER |
		BOOTM_STATE_LOADOS |
#if defined(CONFIG_PPC) || defined(CONFIG_MIPS)
		BOOTM_STATE_OS_CMDLINE |
#endif
		BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
		BOOTM_STATE_OS_GO, &images, 1);
	...
}

继续跟踪到do_bootm_states()里面

//uboot\common\bootm.c
int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
		    int states, bootm_headers_t *images, int boot_progress)
{
    ...
    if (!ret && (states & BOOTM_STATE_FINDOTHER)) {
		ret = bootm_find_other(cmdtp, flag, argc, argv); //找到fdt的最终地址
		argc = 0;	/* consume the args */
	}
    ...
    boot_fn = bootm_os_get_boot_func(images->os.os); //跳转到kernel
    ...
}

这里我们只关心两个主要的函数,分别是

(1)bootm_find_other:找到fdt的最终地址

(2)bootm_os_get_boot_func:找到跳转kernel的函数,并执行它boot_fn()

1、

我们先看一下怎么找到fdt地址的。

//uboot\common\bootm.c
bootm_find_other
    bootm_find_ramdisk_fdt
        bootm_find_fdt

跟踪到最终的 bootm_find_fdt()函数,我们可以看到fdt的地址最终设置到images.ft_addr里面。

/*
 *uboot\common\bootm.c
 */

#if defined(CONFIG_OF_LIBFDT)
static int bootm_find_fdt(int flag, int argc, char * const argv[])
{
	int ret;

	/* find flattened device tree */
	#ifdef CONFIG_DTB_MEM_ADDR
	unsigned long long dtb_mem_addr =  -1;
	char *ft_addr_bak;
	ulong ft_len_bak;
	if (getenv("dtb_mem_addr"))
		dtb_mem_addr = simple_strtoul(getenv("dtb_mem_addr"), NULL, 16);
	else
		dtb_mem_addr = CONFIG_DTB_MEM_ADDR;


	ft_addr_bak = (char *)images.ft_addr;
	ft_len_bak = images.ft_len;
	images.ft_addr = (char *)map_sysmem(dtb_mem_addr, 0);
	images.ft_len = fdt_get_header(dtb_mem_addr, totalsize);
#endif
	printf("load dtb from 0x%lx ......\n", (unsigned long)(images.ft_addr));
    
        ...

	if (ret) {
		puts("Could not find a valid device tree\n");
		return 1;
	}

	set_working_fdt_addr(images.ft_addr);

	return 0;
}
#endif

2、

       上面既然找到了合法的fdt地址,后面肯定就是看怎么传递给kernel了,所以我们再回到bootm_os_get_boot_func()。看一下里面做了什么动作。

/*
 * uboot\common\bootm_os.c
 */
boot_os_fn *bootm_os_get_boot_func(int os)
{
        ...
	return boot_os[os];
}

其实,boot_os是一个函数数组,会根据传递进来的os值,调用对应的函数

/*
 * uboot\common\bootm_os.c
 */
static boot_os_fn *boot_os[] = {
    ...
#ifdef CONFIG_BOOTM_LINUX
	[IH_OS_LINUX] = do_bootm_linux,
#endif
    ...
}

所以最终调用到do_bootm_linux()函数里面,并最终调用到kernel_entry(),并去执行kernel。

/*
 *uboot\arch\arm\lib\bootm.c
 */
do_bootm_linux
    boot_jump_linux(images, flag);
        kernel_entry(images->ft_addr, NULL, NULL, NULL); //从uboot跳到执行kernel,第一个参数就是fdt的地址

kernel阶段

       分析完上面的uboot阶段后,我们已经知道fdt的地址是作为参数1传递到kernel。下面看一下kernel阶段怎么获取这个地址值的。

       大致看一下head.S里面的内容吧,全是汇编,只能懂个大概(囧~)。下面贴出来的代码可以看出来,uboot的传过来的地址是怎么传给kernel来的,其它的汇编代码可以参考我给出的链接。

/*
 * kernel\arch\arm64\kernel\head.S
 * 可以参考下面两篇文章:
 * 	https://www.cnblogs.com/keanuyaoo/p/3299536.html
 *	http://www.wowotech.net/215.html
 */
ENTRY(stext)
	mov	x21, x0				// x21=FDT,uboot阶段我们知道fdt的地址放在了寄存器x0,这里赋值给x21
	bl	el2_setup			// Drop to EL1, w20=cpu_boot_mode
	bl	__calc_phys_offset		// x24=PHYS_OFFSET, x28=PHYS_OFFSET-PAGE_OFFSET
	bl	set_cpu_boot_mode_flag
	mrs	x22, midr_el1			// x22=cpuid
	mov	x0, x22
	bl	lookup_processor_type
	mov	x23, x0				// x23=current cpu_table
	cbz	x23, __error_p			// invalid processor (x23=0)?
	bl	__vet_fdt
	bl	__create_page_tables		// x25=TTBR0, x26=TTBR1

    ...

__switch_data:
	.quad	__mmap_switched
	.quad	__bss_start			// x6
	.quad	_end				// x7
	.quad	processor_id			// x4
	.quad	__fdt_pointer			// x5,用来指向FDT的首地址
	.quad	memstart_addr			// x6
	.quad	init_thread_union + THREAD_START_SP // sp

/*
 * The following fragment of code is executed with the MMU on in MMU mode, and
 * uses absolute addresses; this is not position independent.
 */
__mmap_switched:
	adr	x3, __switch_data + 8

	ldp	x6, x7, [x3], #16
1:	cmp	x6, x7
	b.hs	2f
	str	xzr, [x6], #8			// Clear BSS
	b	1b
2:
	ldp	x4, x5, [x3], #16
	ldr	x6, [x3], #8
	ldr	x16, [x3]
	mov	sp, x16
	str	x22, [x4]			// Save processor ID
	str	x21, [x5]			// Save FDT pointer,保存FDT的地址到x5指向的地址
	str	x24, [x6]			// Save PHYS_OFFSET
	mov	x29, #0
	b	start_kernel //跳转到C代码继续执行
ENDPROC(__mmap_switched)

在汇编的阶段,大概可以看出来用变量__fdt_pointer指向FDT的首地址,执行完汇编的阶段就会调到C代码的流程里面了。

asmlinkage void __init start_kernel(void)
{
    ...
    setup_arch(&command_line); //设置架构相关的内容
    ...
}

 我们只关心读取fdt的相关内容,直接进到setup_ arch()函数。

void __init setup_arch(char **cmdline_p)
{
	/*
	 * Unmask asynchronous aborts early to catch possible system errors.
	 */
	local_async_enable();

	setup_processor();

	setup_machine_fdt(__fdt_pointer); //根据fdt地址,进行fdt的扫描
}

终于看到fdt的地址被用起来了,传递进了setup_machine_fdt()函数里面。

static void __init setup_machine_fdt(phys_addr_t dt_phys)
{
	if (!dt_phys || !early_init_dt_scan(phys_to_virt(dt_phys))) {
		early_print("\n"
			"Error: invalid device tree blob at physical address 0x%p (virtual address 0x%p)\n"
			"The dtb must be 8-byte aligned and passed in the first 512MB of memory\n"
			"\nPlease check your bootloader.\n",
			dt_phys, phys_to_virt(dt_phys));

		while (true)
			cpu_relax();
	}

	machine_name = of_flat_dt_get_machine_name();
}

 因为传进来的fdt地址是物理地址,所以用phys_to_virt()函数转换为虚拟地址,并进入到early_init_dt_scan()进行fdt的扫描

bool __init early_init_dt_scan(void *params)
{
	if (!params)
		return false;

	/* Setup flat device-tree pointer */
	initial_boot_params = params;

	/* check device tree validity */
	if (be32_to_cpu(initial_boot_params->magic) != OF_DT_HEADER) {
		initial_boot_params = NULL;
		return false;
	}

	/* 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);

	return true;
}

具体的扫描函数就不分析了,有兴趣的同学可以继续跟踪代码。

你可能感兴趣的:(嵌入式学习,DTB,ARM64,DTB)