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

【ARM64】DTB地址从uboot传递到kernel的流程前言 nbsp nbsp nbsp nbsp 最近在移植一款蓝牙芯片的过程中用到了设备树 一开始在研究 kernel 是怎么解析 DTB 的 后来就很好奇 kernel 是怎么找到 DTB 的 所以就有了这篇文章 纯粹记录一下自己的学习过程吧 正文 nbsp nbsp nbsp nbsp 下面我就从两个阶段来讲述 第一个阶段就是 uboot 是怎么将 DTB 的地址传递给 kernel 第二个阶段就是 kernel 怎么根据 DTB 的地址 并解析 DTB 的 uboot 阶

前言

       最近在移植一款蓝牙芯片的过程中用到了设备树,一开始在研究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; }

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/211166.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 下午11:03
下一篇 2026年3月18日 下午11:04


相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号